mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-08-17 14:51:04 +02:00
Merge pull request #873 from JakeStanger/feat/launcher-paginate
Launcher pagination and label truncation options
This commit is contained in:
commit
9b67719cac
7 changed files with 322 additions and 39 deletions
|
@ -55,10 +55,15 @@ A text label. Pango markup is supported.
|
||||||
> Type `label`
|
> Type `label`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|---------------|------------------------------------------------------------|----------------|----------------------------------------------------------------------|
|
|-----------------------|------------------------------------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. |
|
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. |
|
||||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label text. |
|
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label text. |
|
||||||
| `justify` | `'left'`, `'right'`, `'center'`, or `'fill'` | `'left'` | Justification (alignment) of the label text. |
|
| `justify` | `'left'`, `'right'`, `'center'`, or `'fill'` | `'left'` | Justification (alignment) of the label text. |
|
||||||
|
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||||
|
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||||
|
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
||||||
|
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
|
|
||||||
|
|
||||||
#### Button
|
#### Button
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,13 @@ For more advanced use-cases, use [custom](custom).
|
||||||
> Type: `label`
|
> Type: `label`
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|---------|-------------------------------------------------|---------|------------------------|
|
|-----------------------|------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Text to show on label. |
|
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Text to show on label. |
|
||||||
|
| `truncate` | `'start'` or `'middle'` or `'end'` or `off` or `Map` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||||
|
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `off` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||||
|
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
||||||
|
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
|
@ -14,21 +14,22 @@ Optionally displays a launchable set of favourites.
|
||||||
> Type: `launcher`
|
> Type: `launcher`
|
||||||
|
|
||||||
| | Type | Default | Description |
|
| | Type | Default | Description |
|
||||||
|-----------------------------|---------------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------------|---------------------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher. |
|
| `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher. |
|
||||||
| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
|
| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
|
||||||
| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
||||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||||
| `reversed` | `boolean` | `false` | Whether to reverse the order of favorites/items |
|
| `reversed` | `boolean` | `false` | Whether to reverse the order of favorites/items |
|
||||||
| `minimize_focused` | `boolean` | `true` | Whether to minimize a focused window when its icon is clicked. Only minimizes single windows. |
|
| `minimize_focused` | `boolean` | `true` | Whether to minimize a focused window when its icon is clicked. Only minimizes single windows. |
|
||||||
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `end` | The location of the ellipses and where to truncate text from. Applies to application names when `show_names` is enabled. |
|
| `truncate.mode` | `'start'` or `'middle'` or `'end'` or `off` | `end` | Location of the ellipses and where to truncate text from. Applies to application names when `show_names` is enabled. |
|
||||||
| `truncate.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
| `truncate.length` | `integer` | `null` | Fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
||||||
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
| `truncate.max_length` | `integer` | `null` | Maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
| `truncate_popup.mode` | `'start'` or `'middle'` or `'end'` or `off` | `middle` | The location of the ellipses and where to truncate text from. Applies to window names within a group popup. |
|
| `truncate_popup.mode` | `'start'` or `'middle'` or `'end'` or `off` | `middle` | Location of the ellipses and where to truncate text from. Applies to window names within a group popup. |
|
||||||
| `truncate_popup.length` | `integer` | `null` | The fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
| `truncate_popup.length` | `integer` | `null` | Fixed width (in chars) of the widget. Leave blank to let GTK automatically handle. |
|
||||||
| `truncate_popup.max_length` | `integer` | `25` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
| `truncate_popup.max_length` | `integer` | `25` | Maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||||
|
| `page_size` | `integer` | `1000` | Number of items to show on a page. When the number of items is reached, controls appear which can be used to move forward/back through the list of items. |
|
||||||
|
| `icons.page_back` | `string` or [image](images) | `` | Icon to show for page back button. |
|
||||||
|
| `icons.page_forward` | `string` or [image](images) | `` | Icon to show for page forward button. |
|
||||||
<details>
|
<details>
|
||||||
<summary>JSON</summary>
|
<summary>JSON</summary>
|
||||||
|
|
||||||
|
@ -105,12 +106,15 @@ start:
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
| Selector | Description |
|
| Selector | Description |
|
||||||
|-------------------------------|--------------------------|
|
|--------------------------------------|---------------------------|
|
||||||
| `.launcher` | Launcher widget box |
|
| `.launcher` | Launcher widget box |
|
||||||
| `.launcher .item` | App button |
|
| `.launcher .item` | App button |
|
||||||
| `.launcher .item.open` | App button (open app) |
|
| `.launcher .item.open` | App button (open app) |
|
||||||
| `.launcher .item.focused` | App button (focused app) |
|
| `.launcher .item.focused` | App button (focused app) |
|
||||||
| `.launcher .item.urgent` | App button (urgent app) |
|
| `.launcher .item.urgent` | App button (urgent app) |
|
||||||
|
| `.launcher .pagination` | Pagination controls box |
|
||||||
|
| `.launcher .pagination .btn-back` | Pagination back button |
|
||||||
|
| `.launcher .pagination .btn-forward` | Pagination forward button |
|
||||||
| `.popup-launcher` | Popup container |
|
| `.popup-launcher` | Popup container |
|
||||||
| `.popup-launcher .popup-item` | Window button in popup |
|
| `.popup-launcher .popup-item` | Window button in popup |
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use super::{CustomWidget, CustomWidgetContext};
|
use super::{CustomWidget, CustomWidgetContext};
|
||||||
use crate::build;
|
use crate::build;
|
||||||
use crate::config::{ModuleJustification, ModuleOrientation};
|
use crate::config::{ModuleJustification, ModuleOrientation, TruncateMode};
|
||||||
use crate::dynamic_value::dynamic_string;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::gtk_helpers::IronbarLabelExt;
|
use crate::gtk_helpers::IronbarLabelExt;
|
||||||
|
|
||||||
|
@ -44,6 +44,11 @@ pub struct LabelWidget {
|
||||||
/// **Default**: `left`
|
/// **Default**: `left`
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
justify: ModuleJustification,
|
justify: ModuleJustification,
|
||||||
|
|
||||||
|
/// See [truncate options](module-level-options#truncate-mode).
|
||||||
|
///
|
||||||
|
/// **Default**: `null`
|
||||||
|
truncate: Option<TruncateMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomWidget for LabelWidget {
|
impl CustomWidget for LabelWidget {
|
||||||
|
@ -56,6 +61,10 @@ impl CustomWidget for LabelWidget {
|
||||||
label.set_justify(self.justify.into());
|
label.set_justify(self.justify.into());
|
||||||
label.set_use_markup(true);
|
label.set_use_markup(true);
|
||||||
|
|
||||||
|
if let Some(truncate) = self.truncate {
|
||||||
|
label.truncate(truncate);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let label = label.clone();
|
let label = label.clone();
|
||||||
dynamic_string(&self.label, move |string| {
|
dynamic_string(&self.label, move |string| {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config::CommonConfig;
|
use crate::config::{CommonConfig, TruncateMode};
|
||||||
use crate::dynamic_value::dynamic_string;
|
use crate::dynamic_value::dynamic_string;
|
||||||
use crate::gtk_helpers::IronbarLabelExt;
|
use crate::gtk_helpers::IronbarLabelExt;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||||
|
@ -17,6 +17,12 @@ pub struct LabelModule {
|
||||||
/// **Required**
|
/// **Required**
|
||||||
label: String,
|
label: String,
|
||||||
|
|
||||||
|
// -- Common --
|
||||||
|
/// See [truncate options](module-level-options#truncate-mode).
|
||||||
|
///
|
||||||
|
/// **Default**: `null`
|
||||||
|
truncate: Option<TruncateMode>,
|
||||||
|
|
||||||
/// See [common options](module-level-options#common-options).
|
/// See [common options](module-level-options#common-options).
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: Option<CommonConfig>,
|
pub common: Option<CommonConfig>,
|
||||||
|
@ -26,6 +32,7 @@ impl LabelModule {
|
||||||
pub(crate) fn new(label: String) -> Self {
|
pub(crate) fn new(label: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
|
truncate: None,
|
||||||
common: Some(CommonConfig::default()),
|
common: Some(CommonConfig::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +65,10 @@ impl Module<Label> for LabelModule {
|
||||||
) -> Result<ModuleParts<Label>> {
|
) -> Result<ModuleParts<Label>> {
|
||||||
let label = Label::builder().use_markup(true).build();
|
let label = Label::builder().use_markup(true).build();
|
||||||
|
|
||||||
|
if let Some(truncate) = self.truncate {
|
||||||
|
label.truncate(truncate);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let label = label.clone();
|
let label = label.clone();
|
||||||
glib_recv!(context.subscribe(), string => label.set_label_escaped(&string));
|
glib_recv!(context.subscribe(), string => label.set_label_escaped(&string));
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
mod item;
|
mod item;
|
||||||
mod open_state;
|
mod open_state;
|
||||||
|
mod pagination;
|
||||||
|
|
||||||
use self::item::{AppearanceOptions, Item, ItemButton, Window};
|
use self::item::{AppearanceOptions, Item, ItemButton, Window};
|
||||||
use self::open_state::OpenState;
|
use self::open_state::OpenState;
|
||||||
|
@ -9,12 +10,14 @@ use crate::config::{CommonConfig, EllipsizeMode, TruncateMode};
|
||||||
use crate::desktop_file::find_desktop_file;
|
use crate::desktop_file::find_desktop_file;
|
||||||
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
|
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
|
||||||
use crate::modules::launcher::item::ImageTextButton;
|
use crate::modules::launcher::item::ImageTextButton;
|
||||||
|
use crate::modules::launcher::pagination::{IconContext, Pagination};
|
||||||
use crate::{arc_mut, glib_recv, lock, module_impl, send_async, spawn, try_send, write_lock};
|
use crate::{arc_mut, glib_recv, lock, module_impl, send_async, spawn, try_send, write_lock};
|
||||||
use color_eyre::{Help, Report};
|
use color_eyre::{Help, Report};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, Orientation};
|
use gtk::{Button, Orientation};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
|
@ -62,6 +65,31 @@ pub struct LauncherModule {
|
||||||
#[serde(default = "crate::config::default_true")]
|
#[serde(default = "crate::config::default_true")]
|
||||||
minimize_focused: bool,
|
minimize_focused: bool,
|
||||||
|
|
||||||
|
/// The number of items to show on a page.
|
||||||
|
///
|
||||||
|
/// When the number of items reaches the page size,
|
||||||
|
/// pagination controls appear at the start of the widget
|
||||||
|
/// which can be used to move forward/back through the list of items.
|
||||||
|
///
|
||||||
|
/// If there are too many to fit, the overflow will be truncated
|
||||||
|
/// by the next widget.
|
||||||
|
///
|
||||||
|
/// **Default**: `1000`.
|
||||||
|
#[serde(default = "default_page_size")]
|
||||||
|
page_size: usize,
|
||||||
|
|
||||||
|
/// Module UI icons (separate from app icons shown for items).
|
||||||
|
///
|
||||||
|
/// See [icons](#icons).
|
||||||
|
#[serde(default)]
|
||||||
|
icons: Icons,
|
||||||
|
|
||||||
|
/// Size in pixels to render pagination icons at (image icons only).
|
||||||
|
///
|
||||||
|
/// **Default**: `16`
|
||||||
|
#[serde(default = "default_icon_size_pagination")]
|
||||||
|
pagination_icon_size: i32,
|
||||||
|
|
||||||
// -- common --
|
// -- common --
|
||||||
/// Truncate application names on the bar if they get too long.
|
/// Truncate application names on the bar if they get too long.
|
||||||
/// See [truncate options](module-level-options#truncate-mode).
|
/// See [truncate options](module-level-options#truncate-mode).
|
||||||
|
@ -82,10 +110,51 @@ pub struct LauncherModule {
|
||||||
pub common: Option<CommonConfig>,
|
pub common: Option<CommonConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
struct Icons {
|
||||||
|
/// Icon to show for page back button.
|
||||||
|
///
|
||||||
|
/// **Default**: ``
|
||||||
|
#[serde(default = "default_icon_page_back")]
|
||||||
|
page_back: String,
|
||||||
|
|
||||||
|
/// Icon to show for page back button.
|
||||||
|
///
|
||||||
|
/// **Default**: `>`
|
||||||
|
#[serde(default = "default_icon_page_forward")]
|
||||||
|
page_forward: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Icons {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
page_back: default_icon_page_back(),
|
||||||
|
page_forward: default_icon_page_forward(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fn default_icon_size() -> i32 {
|
const fn default_icon_size() -> i32 {
|
||||||
32
|
32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn default_icon_size_pagination() -> i32 {
|
||||||
|
default_icon_size() / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_page_size() -> usize {
|
||||||
|
1000
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_icon_page_back() -> String {
|
||||||
|
String::from("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_icon_page_forward() -> String {
|
||||||
|
String::from("")
|
||||||
|
}
|
||||||
|
|
||||||
const fn default_truncate_popup() -> TruncateMode {
|
const fn default_truncate_popup() -> TruncateMode {
|
||||||
TruncateMode::Length {
|
TruncateMode::Length {
|
||||||
mode: EllipsizeMode::Middle,
|
mode: EllipsizeMode::Middle,
|
||||||
|
@ -386,6 +455,19 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
let icon_theme = info.icon_theme;
|
let icon_theme = info.icon_theme;
|
||||||
|
|
||||||
let container = gtk::Box::new(info.bar_position.orientation(), 0);
|
let container = gtk::Box::new(info.bar_position.orientation(), 0);
|
||||||
|
let page_size = self.page_size;
|
||||||
|
|
||||||
|
let pagination = Pagination::new(
|
||||||
|
&container,
|
||||||
|
self.page_size,
|
||||||
|
info.bar_position.orientation(),
|
||||||
|
IconContext {
|
||||||
|
icon_back: &self.icons.page_back,
|
||||||
|
icon_fwd: &self.icons.page_forward,
|
||||||
|
icon_size: self.pagination_icon_size,
|
||||||
|
icon_theme: info.icon_theme,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
let container = container.clone();
|
let container = container.clone();
|
||||||
|
@ -407,7 +489,15 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
|
|
||||||
let tx = context.tx.clone();
|
let tx = context.tx.clone();
|
||||||
let rx = context.subscribe();
|
let rx = context.subscribe();
|
||||||
glib_recv!(rx, event => {
|
|
||||||
|
let mut handle_event = move |event: LauncherUpdate| {
|
||||||
|
// all widgets show by default
|
||||||
|
// so check if pagination should be shown
|
||||||
|
// to ensure correct state on init.
|
||||||
|
if buttons.len() <= page_size {
|
||||||
|
pagination.hide();
|
||||||
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
LauncherUpdate::AddItem(item) => {
|
LauncherUpdate::AddItem(item) => {
|
||||||
debug!("Adding item with id '{}' to the bar: {item:?}", item.app_id);
|
debug!("Adding item with id '{}' to the bar: {item:?}", item.app_id);
|
||||||
|
@ -426,9 +516,18 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.reversed {
|
if self.reversed {
|
||||||
container.pack_end(&button.button.button, false, false, 0);
|
container.pack_end(button.button.deref(), false, false, 0);
|
||||||
} else {
|
} else {
|
||||||
container.add(&button.button.button);
|
container.add(button.button.deref());
|
||||||
|
}
|
||||||
|
|
||||||
|
if buttons.len() + 1 >= pagination.offset() + page_size {
|
||||||
|
button.button.set_visible(false);
|
||||||
|
pagination.set_sensitive_fwd(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if buttons.len() + 1 > page_size {
|
||||||
|
pagination.show_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
buttons.insert(item.app_id, button);
|
buttons.insert(item.app_id, button);
|
||||||
|
@ -456,6 +555,14 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
buttons.shift_remove(&app_id);
|
buttons.shift_remove(&app_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if buttons.len() < pagination.offset() + page_size {
|
||||||
|
pagination.set_sensitive_fwd(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if buttons.len() <= page_size {
|
||||||
|
pagination.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
LauncherUpdate::RemoveWindow(app_id, win_id) => {
|
LauncherUpdate::RemoveWindow(app_id, win_id) => {
|
||||||
debug!("Removing window {win_id} with id {app_id}");
|
debug!("Removing window {win_id} with id {app_id}");
|
||||||
|
@ -485,7 +592,9 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
}
|
}
|
||||||
LauncherUpdate::Hover(_) => {}
|
LauncherUpdate::Hover(_) => {}
|
||||||
};
|
};
|
||||||
});
|
};
|
||||||
|
|
||||||
|
glib_recv!(rx, handle_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rx = context.subscribe();
|
let rx = context.subscribe();
|
||||||
|
|
140
src/modules/launcher/pagination.rs
Normal file
140
src/modules/launcher/pagination.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use crate::gtk_helpers::IronbarGtkExt;
|
||||||
|
use crate::image::new_icon_button;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::{Button, IconTheme, Orientation};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub struct Pagination {
|
||||||
|
offset: Rc<RefCell<usize>>,
|
||||||
|
|
||||||
|
controls_container: gtk::Box,
|
||||||
|
btn_fwd: Button,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IconContext<'a> {
|
||||||
|
pub icon_back: &'a str,
|
||||||
|
pub icon_fwd: &'a str,
|
||||||
|
pub icon_size: i32,
|
||||||
|
pub icon_theme: &'a IconTheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pagination {
|
||||||
|
pub fn new(
|
||||||
|
container: >k::Box,
|
||||||
|
page_size: usize,
|
||||||
|
orientation: Orientation,
|
||||||
|
icon_context: IconContext,
|
||||||
|
) -> Self {
|
||||||
|
let scroll_box = gtk::Box::new(orientation, 0);
|
||||||
|
|
||||||
|
let scroll_back = new_icon_button(
|
||||||
|
icon_context.icon_back,
|
||||||
|
icon_context.icon_theme,
|
||||||
|
icon_context.icon_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
let scroll_fwd = new_icon_button(
|
||||||
|
icon_context.icon_fwd,
|
||||||
|
icon_context.icon_theme,
|
||||||
|
icon_context.icon_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
scroll_back.set_sensitive(false);
|
||||||
|
scroll_fwd.set_sensitive(false);
|
||||||
|
|
||||||
|
scroll_box.add_class("pagination");
|
||||||
|
scroll_back.add_class("btn-back");
|
||||||
|
scroll_fwd.add_class("btn-forward");
|
||||||
|
|
||||||
|
scroll_box.add(&scroll_back);
|
||||||
|
scroll_box.add(&scroll_fwd);
|
||||||
|
container.add(&scroll_box);
|
||||||
|
|
||||||
|
let offset = Rc::new(RefCell::new(1));
|
||||||
|
|
||||||
|
{
|
||||||
|
let offset = offset.clone();
|
||||||
|
let container = container.clone();
|
||||||
|
let scroll_back = scroll_back.clone();
|
||||||
|
|
||||||
|
scroll_fwd.connect_clicked(move |btn| {
|
||||||
|
let mut offset = offset.borrow_mut();
|
||||||
|
let child_count = container.children().len();
|
||||||
|
|
||||||
|
*offset = std::cmp::min(child_count - 1, *offset + page_size);
|
||||||
|
|
||||||
|
Self::update_page(&container, *offset, page_size);
|
||||||
|
|
||||||
|
if *offset + page_size >= child_count {
|
||||||
|
btn.set_sensitive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll_back.set_sensitive(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let offset = offset.clone();
|
||||||
|
let container = container.clone();
|
||||||
|
let scroll_fwd = scroll_fwd.clone();
|
||||||
|
|
||||||
|
scroll_back.connect_clicked(move |btn| {
|
||||||
|
let mut offset = offset.borrow_mut();
|
||||||
|
// avoid using std::cmp::max due to possible overflow
|
||||||
|
if page_size < *offset {
|
||||||
|
*offset -= page_size;
|
||||||
|
} else {
|
||||||
|
*offset = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::update_page(&container, *offset, page_size);
|
||||||
|
|
||||||
|
if *offset == 1 || *offset - page_size < 1 {
|
||||||
|
btn.set_sensitive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll_fwd.set_sensitive(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
offset,
|
||||||
|
|
||||||
|
controls_container: scroll_box,
|
||||||
|
btn_fwd: scroll_fwd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_page(container: >k::Box, offset: usize, page_size: usize) {
|
||||||
|
for (i, btn) in container.children().iter().enumerate() {
|
||||||
|
// skip offset buttons
|
||||||
|
if i == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= offset && i < offset + page_size {
|
||||||
|
btn.show();
|
||||||
|
} else {
|
||||||
|
btn.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sensitive_fwd(&self, sensitive: bool) {
|
||||||
|
self.btn_fwd.set_sensitive(sensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
*self.offset.borrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Pagination {
|
||||||
|
type Target = gtk::Box;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.controls_container
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue