mirror of
https://github.com/JakeStanger/ironbar.git
synced 2024-11-25 02:50:31 +03:00
feat(launcher): truncate
and truncate_popup
config options
This commit is contained in:
parent
8b05ed526d
commit
da13b9d500
@ -12,13 +12,21 @@ Optionally displays a launchable set of favourites.
|
||||
|
||||
> Type: `launcher`
|
||||
|
||||
| | Type | Default | Description |
|
||||
|--------------|------------|---------|-----------------------------------------------------------------------------------------------------|
|
||||
| `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_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||
| `reversed` | `boolean` | `false` | Whether to reverse the order of favorites/items |
|
||||
| | Type | Default | Description |
|
||||
|-----------------------------|---------------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------|
|
||||
| `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_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
||||
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
|
||||
| `reversed` | `boolean` | `false` | Whether to reverse the order of favorites/items |
|
||||
| `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.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. |
|
||||
| `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.length` | `integer` | `null` | The 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. |
|
||||
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
|
||||
|
@ -44,7 +44,7 @@ use std::collections::HashMap;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
pub use self::common::{CommonConfig, ModuleOrientation, TransitionType};
|
||||
pub use self::truncate::TruncateMode;
|
||||
pub use self::truncate::{EllipsizeMode, TruncateMode};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
|
@ -1,13 +1,14 @@
|
||||
use gtk::pango::EllipsizeMode as GtkEllipsizeMode;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
#[derive(Debug, Deserialize, Clone, Copy, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
pub enum EllipsizeMode {
|
||||
None,
|
||||
Start,
|
||||
Middle,
|
||||
#[default]
|
||||
End,
|
||||
}
|
||||
|
||||
@ -28,6 +29,8 @@ impl From<EllipsizeMode> for GtkEllipsizeMode {
|
||||
///
|
||||
/// The option can be configured in one of two modes.
|
||||
///
|
||||
/// **Default**: `Auto (end)`
|
||||
///
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
@ -56,7 +59,7 @@ pub enum TruncateMode {
|
||||
///
|
||||
/// **Valid options**: `start`, `middle`, `end`
|
||||
/// <br>
|
||||
/// **Default**: `null`
|
||||
/// **Default**: `end`
|
||||
Auto(EllipsizeMode),
|
||||
|
||||
/// Length mode defines a fixed point at which to ellipsize.
|
||||
@ -100,6 +103,12 @@ pub enum TruncateMode {
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for TruncateMode {
|
||||
fn default() -> Self {
|
||||
Self::Auto(EllipsizeMode::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl TruncateMode {
|
||||
pub const fn length(&self) -> Option<i32> {
|
||||
match self {
|
||||
|
@ -1,15 +1,16 @@
|
||||
use super::open_state::OpenState;
|
||||
use crate::clients::wayland::ToplevelInfo;
|
||||
use crate::config::BarPosition;
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::config::{BarPosition, TruncateMode};
|
||||
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
||||
use crate::modules::ModuleUpdateEvent;
|
||||
use crate::{read_lock, try_send};
|
||||
use glib::Propagation;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme};
|
||||
use gtk::{Button, IconTheme, Image, Label, Orientation};
|
||||
use indexmap::IndexMap;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::sync::RwLock;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
@ -134,7 +135,7 @@ pub struct MenuState {
|
||||
}
|
||||
|
||||
pub struct ItemButton {
|
||||
pub button: Button,
|
||||
pub button: ImageTextButton,
|
||||
pub persistent: bool,
|
||||
pub show_names: bool,
|
||||
pub menu_state: Rc<RwLock<MenuState>>,
|
||||
@ -145,6 +146,7 @@ pub struct AppearanceOptions {
|
||||
pub show_names: bool,
|
||||
pub show_icons: bool,
|
||||
pub icon_size: i32,
|
||||
pub truncate: TruncateMode,
|
||||
}
|
||||
|
||||
impl ItemButton {
|
||||
@ -156,16 +158,14 @@ impl ItemButton {
|
||||
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
|
||||
controller_tx: &Sender<ItemEvent>,
|
||||
) -> Self {
|
||||
let mut button = Button::builder();
|
||||
let button = ImageTextButton::new();
|
||||
|
||||
if appearance.show_names {
|
||||
button = button.label(&item.name);
|
||||
button.label.set_label(&item.name);
|
||||
button.label.truncate(appearance.truncate);
|
||||
}
|
||||
|
||||
let button = button.build();
|
||||
|
||||
if appearance.show_icons {
|
||||
let gtk_image = gtk::Image::new();
|
||||
let input = if item.app_id.is_empty() {
|
||||
item.name.clone()
|
||||
} else {
|
||||
@ -173,26 +173,24 @@ impl ItemButton {
|
||||
};
|
||||
let image = ImageProvider::parse(&input, icon_theme, true, appearance.icon_size);
|
||||
if let Some(image) = image {
|
||||
button.set_image(Some(>k_image));
|
||||
button.set_always_show_image(true);
|
||||
|
||||
if let Err(err) = image.load_into_image(>k_image) {
|
||||
if let Err(err) = image.load_into_image(&button.image) {
|
||||
error!("{err:?}");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let style_context = button.style_context();
|
||||
style_context.add_class("item");
|
||||
button.add_class("item");
|
||||
|
||||
if item.favorite {
|
||||
style_context.add_class("favorite");
|
||||
button.add_class("favorite");
|
||||
}
|
||||
if item.open_state.is_open() {
|
||||
style_context.add_class("open");
|
||||
button.add_class("open");
|
||||
}
|
||||
if item.open_state.is_focused() {
|
||||
style_context.add_class("focused");
|
||||
button.add_class("focused");
|
||||
}
|
||||
|
||||
{
|
||||
@ -297,3 +295,39 @@ impl ItemButton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImageTextButton {
|
||||
pub(crate) button: Button,
|
||||
pub(crate) label: Label,
|
||||
image: Image,
|
||||
}
|
||||
|
||||
impl ImageTextButton {
|
||||
pub(crate) fn new() -> Self {
|
||||
let button = Button::new();
|
||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
||||
|
||||
let label = Label::new(None);
|
||||
let image = Image::new();
|
||||
|
||||
button.add(&container);
|
||||
|
||||
container.add(&image);
|
||||
container.add(&label);
|
||||
|
||||
ImageTextButton {
|
||||
button,
|
||||
label,
|
||||
image,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ImageTextButton {
|
||||
type Target = Button;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.button
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ use self::item::{AppearanceOptions, Item, ItemButton, Window};
|
||||
use self::open_state::OpenState;
|
||||
use super::{Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::clients::wayland::{self, ToplevelEvent};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::config::{CommonConfig, EllipsizeMode, TruncateMode};
|
||||
use crate::desktop_file::find_desktop_file;
|
||||
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
|
||||
use crate::modules::launcher::item::ImageTextButton;
|
||||
use crate::{arc_mut, glib_recv, lock, module_impl, send_async, spawn, try_send, write_lock};
|
||||
use color_eyre::{Help, Report};
|
||||
use gtk::prelude::*;
|
||||
@ -54,6 +56,21 @@ pub struct LauncherModule {
|
||||
#[serde(default = "crate::config::default_false")]
|
||||
reversed: bool,
|
||||
|
||||
// -- common --
|
||||
/// Truncate application names on the bar if they get too long.
|
||||
/// See [truncate options](module-level-options#truncate-mode).
|
||||
///
|
||||
/// **Default**: `Auto (end)`
|
||||
#[serde(default)]
|
||||
truncate: TruncateMode,
|
||||
|
||||
/// Truncate application names in popups if they get too long.
|
||||
/// See [truncate options](module-level-options#truncate-mode).
|
||||
///
|
||||
/// **Default**: `{ mode = "middle" max_length = 25 }`
|
||||
#[serde(default = "default_truncate_popup")]
|
||||
truncate_popup: TruncateMode,
|
||||
|
||||
/// See [common options](module-level-options#common-options).
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
@ -63,6 +80,14 @@ const fn default_icon_size() -> i32 {
|
||||
32
|
||||
}
|
||||
|
||||
const fn default_truncate_popup() -> TruncateMode {
|
||||
TruncateMode::Length {
|
||||
mode: EllipsizeMode::Middle,
|
||||
length: None,
|
||||
max_length: Some(25),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LauncherUpdate {
|
||||
/// Adds item
|
||||
@ -342,6 +367,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
show_names: self.show_names,
|
||||
show_icons: self.show_icons,
|
||||
icon_size: self.icon_size,
|
||||
truncate: self.truncate,
|
||||
};
|
||||
|
||||
let show_names = self.show_names;
|
||||
@ -370,9 +396,9 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
);
|
||||
|
||||
if self.reversed {
|
||||
container.pack_end(&button.button, false, false, 0);
|
||||
container.pack_end(&button.button.button, false, false, 0);
|
||||
} else {
|
||||
container.add(&button.button);
|
||||
container.add(&button.button.button);
|
||||
}
|
||||
|
||||
buttons.insert(item.app_id, button);
|
||||
@ -393,10 +419,10 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
if button.persistent {
|
||||
button.set_open(false);
|
||||
if button.show_names {
|
||||
button.button.set_label(&app_id);
|
||||
button.button.label.set_label(&app_id);
|
||||
}
|
||||
} else {
|
||||
container.remove(&button.button);
|
||||
container.remove(&button.button.button);
|
||||
buttons.shift_remove(&app_id);
|
||||
}
|
||||
}
|
||||
@ -423,7 +449,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
|
||||
if show_names {
|
||||
if let Some(button) = buttons.get(&app_id) {
|
||||
button.button.set_label(&name);
|
||||
button.button.label.set_label(&name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -459,7 +485,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
placeholder.set_width_request(MAX_WIDTH);
|
||||
container.add(&placeholder);
|
||||
|
||||
let mut buttons = IndexMap::<String, IndexMap<usize, Button>>::new();
|
||||
let mut buttons = IndexMap::<String, IndexMap<usize, ImageTextButton>>::new();
|
||||
|
||||
{
|
||||
let container = container.clone();
|
||||
@ -473,10 +499,11 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
.windows
|
||||
.into_iter()
|
||||
.map(|(_, win)| {
|
||||
let button = Button::builder()
|
||||
.label(clamp(&win.name))
|
||||
.height_request(40)
|
||||
.build();
|
||||
// TODO: Currently has a useless image
|
||||
let button = ImageTextButton::new();
|
||||
button.set_height_request(40);
|
||||
button.label.set_label(&win.name);
|
||||
button.label.truncate(self.truncate_popup);
|
||||
|
||||
{
|
||||
let tx = controller_tx.clone();
|
||||
@ -498,10 +525,11 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
);
|
||||
|
||||
if let Some(buttons) = buttons.get_mut(&app_id) {
|
||||
let button = Button::builder()
|
||||
.height_request(40)
|
||||
.label(clamp(&win.name))
|
||||
.build();
|
||||
// TODO: Currently has a useless image
|
||||
let button = ImageTextButton::new();
|
||||
button.set_height_request(40);
|
||||
button.label.set_label(&win.name);
|
||||
button.label.truncate(self.truncate_popup);
|
||||
|
||||
{
|
||||
let tx = controller_tx.clone();
|
||||
@ -527,7 +555,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
|
||||
if let Some(buttons) = buttons.get_mut(&app_id) {
|
||||
if let Some(button) = buttons.get(&win_id) {
|
||||
button.set_label(&title);
|
||||
button.label.set_label(&title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -540,8 +568,8 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
// add app's buttons
|
||||
if let Some(buttons) = buttons.get(&app_id) {
|
||||
for (_, button) in buttons {
|
||||
button.style_context().add_class("popup-item");
|
||||
container.add(button);
|
||||
button.add_class("popup-item");
|
||||
container.add(&button.button);
|
||||
}
|
||||
|
||||
container.show_all();
|
||||
@ -556,21 +584,3 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
Some(container)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clamps a string at 24 characters.
|
||||
///
|
||||
/// This is a hacky number derived from
|
||||
/// "what fits inside the 250px popup"
|
||||
/// and probably won't hold up with wide fonts.
|
||||
///
|
||||
/// TODO: Migrate this to truncate system
|
||||
///
|
||||
fn clamp(str: &str) -> String {
|
||||
const MAX_CHARS: usize = 24;
|
||||
|
||||
if str.len() > MAX_CHARS {
|
||||
str.chars().take(MAX_CHARS - 3).collect::<String>() + "..."
|
||||
} else {
|
||||
str.to_string()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user