Merge branch 'pop-os:master' into new-match-algo
This commit is contained in:
commit
01a3789a19
58 changed files with 1360 additions and 567 deletions
|
|
@ -31,7 +31,7 @@ desktop = ["process", "dep:freedesktop-desktop-entry", "dep:mime", "dep:shlex"]
|
||||||
# Enables keycode serialization
|
# Enables keycode serialization
|
||||||
serde-keycode = ["iced_core/serde"]
|
serde-keycode = ["iced_core/serde"]
|
||||||
# Prevents multiple separate process instances.
|
# Prevents multiple separate process instances.
|
||||||
single-instance = ["dep:zbus", "serde", "ron"]
|
single-instance = ["dep:zbus", "ron"]
|
||||||
# smol async runtime
|
# smol async runtime
|
||||||
smol = ["iced/smol", "zbus?/async-io"]
|
smol = ["iced/smol", "zbus?/async-io"]
|
||||||
tokio = [
|
tokio = [
|
||||||
|
|
@ -80,7 +80,7 @@ mime = { version = "0.3.17", optional = true }
|
||||||
nix = { version = "0.27", features = ["process"], optional = true }
|
nix = { version = "0.27", features = ["process"], optional = true }
|
||||||
palette = "0.7.3"
|
palette = "0.7.3"
|
||||||
rfd = { version = "0.14.0", optional = true }
|
rfd = { version = "0.14.0", optional = true }
|
||||||
serde = { version = "1.0.180", optional = true }
|
serde = { version = "1.0.180", features = ["derive"]}
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
textdistance = "1.0.2"
|
textdistance = "1.0.2"
|
||||||
thiserror = "1.0.44"
|
thiserror = "1.0.44"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Integrations for cosmic-config — the cosmic configuration system.
|
||||||
|
|
||||||
use notify::{
|
use notify::{
|
||||||
event::{EventKind, ModifyKind},
|
event::{EventKind, ModifyKind},
|
||||||
Watcher,
|
Watcher,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{to_hex, OutputError};
|
use super::{to_rgba, OutputError};
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -24,95 +24,169 @@ impl Theme {
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let window_bg = to_hex(background.base);
|
let window_bg = to_rgba(background.base);
|
||||||
let window_fg = to_hex(background.on);
|
let window_fg = to_rgba(background.on);
|
||||||
|
|
||||||
let view_bg = to_hex(primary.base);
|
let view_bg = to_rgba(primary.base);
|
||||||
let view_fg = to_hex(primary.on);
|
let view_fg = to_rgba(primary.on);
|
||||||
|
|
||||||
let headerbar_bg = to_hex(background.base);
|
let headerbar_bg = to_rgba(background.base);
|
||||||
let headerbar_fg = to_hex(background.on);
|
let headerbar_fg = to_rgba(background.on);
|
||||||
let headerbar_border_color = to_hex(background.divider);
|
let headerbar_border_color = to_rgba(background.divider);
|
||||||
|
|
||||||
let sidebar_bg = to_hex(primary.base);
|
let sidebar_bg = to_rgba(primary.base);
|
||||||
let sidebar_fg = to_hex(primary.on);
|
let sidebar_fg = to_rgba(primary.on);
|
||||||
let sidebar_shade = to_hex(if self.is_dark {
|
let sidebar_shade = to_rgba(if self.is_dark {
|
||||||
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
||||||
} else {
|
} else {
|
||||||
Rgba::new(0.0, 0.0, 0.0, 0.32)
|
Rgba::new(0.0, 0.0, 0.0, 0.32)
|
||||||
});
|
});
|
||||||
let backdrop_overlay = Srgba::new(1.0, 1.0, 1.0, if self.is_dark { 0.08 } else { 0.32 });
|
let backdrop_overlay = Srgba::new(1.0, 1.0, 1.0, if self.is_dark { 0.08 } else { 0.32 });
|
||||||
let sidebar_backdrop = to_hex(over(backdrop_overlay, primary.base));
|
let sidebar_backdrop = to_rgba(over(backdrop_overlay, primary.base));
|
||||||
|
|
||||||
let secondary_sidebar_bg = to_hex(secondary.base);
|
let secondary_sidebar_bg = to_rgba(secondary.base);
|
||||||
let secondary_sidebar_fg = to_hex(secondary.on);
|
let secondary_sidebar_fg = to_rgba(secondary.on);
|
||||||
let secondary_sidebar_shade = to_hex(if self.is_dark {
|
let secondary_sidebar_shade = to_rgba(if self.is_dark {
|
||||||
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
||||||
} else {
|
} else {
|
||||||
Rgba::new(0.0, 0.0, 0.0, 0.32)
|
Rgba::new(0.0, 0.0, 0.0, 0.32)
|
||||||
});
|
});
|
||||||
let secondary_sidebar_backdrop = to_hex(over(backdrop_overlay, secondary.base));
|
let secondary_sidebar_backdrop = to_rgba(over(backdrop_overlay, secondary.base));
|
||||||
|
|
||||||
let headerbar_backdrop = to_hex(background.base);
|
let headerbar_backdrop = to_rgba(background.base);
|
||||||
|
|
||||||
let card_bg = to_hex(background.component.base);
|
let card_bg = to_rgba(background.component.base);
|
||||||
let card_fg = to_hex(background.component.on);
|
let card_fg = to_rgba(background.component.on);
|
||||||
|
|
||||||
let thumbnail_bg = to_hex(background.component.base);
|
let thumbnail_bg = to_rgba(background.component.base);
|
||||||
let thumbnail_fg = to_hex(background.component.on);
|
let thumbnail_fg = to_rgba(background.component.on);
|
||||||
|
|
||||||
let dialog_bg = to_hex(primary.base);
|
let dialog_bg = to_rgba(primary.base);
|
||||||
let dialog_fg = to_hex(primary.on);
|
let dialog_fg = to_rgba(primary.on);
|
||||||
|
|
||||||
let popover_bg = to_hex(background.component.base);
|
let popover_bg = to_rgba(background.component.base);
|
||||||
let popover_fg = to_hex(background.component.on);
|
let popover_fg = to_rgba(background.component.on);
|
||||||
|
|
||||||
let shade = to_hex(if self.is_dark {
|
let shade = to_rgba(if self.is_dark {
|
||||||
Rgba::new(0.0, 0.0, 0.0, 0.32)
|
Rgba::new(0.0, 0.0, 0.0, 0.32)
|
||||||
} else {
|
} else {
|
||||||
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let window_control_hover_bg = to_rgba(if self.is_dark {
|
||||||
|
Rgba::new(255.0, 255.0, 255.0, 0.12)
|
||||||
|
} else {
|
||||||
|
Rgba::new(0.0, 0.0, 0.0, 0.12)
|
||||||
|
});
|
||||||
|
let corner_radius = self.corner_radii.radius_s[0];
|
||||||
let mut inverted_bg_divider = background.base;
|
let mut inverted_bg_divider = background.base;
|
||||||
inverted_bg_divider.alpha = 0.5;
|
inverted_bg_divider.alpha = 0.5;
|
||||||
let scrollbar_outline = to_hex(inverted_bg_divider);
|
let scrollbar_outline = to_rgba(inverted_bg_divider);
|
||||||
|
|
||||||
let mut css = format! {r#"
|
let mut css = format! {r#"
|
||||||
@define-color window_bg_color #{window_bg};
|
@define-color window_bg_color {window_bg};
|
||||||
@define-color window_fg_color #{window_fg};
|
@define-color window_fg_color {window_fg};
|
||||||
|
|
||||||
@define-color view_bg_color #{view_bg};
|
@define-color view_bg_color {view_bg};
|
||||||
@define-color view_fg_color #{view_fg};
|
@define-color view_fg_color {view_fg};
|
||||||
|
|
||||||
@define-color headerbar_bg_color #{headerbar_bg};
|
@define-color headerbar_bg_color {headerbar_bg};
|
||||||
@define-color headerbar_fg_color #{headerbar_fg};
|
@define-color headerbar_fg_color {headerbar_fg};
|
||||||
@define-color headerbar_border_color_color #{headerbar_border_color};
|
@define-color headerbar_border_color_color {headerbar_border_color};
|
||||||
@define-color headerbar_backdrop_color #{headerbar_backdrop};
|
@define-color headerbar_backdrop_color {headerbar_backdrop};
|
||||||
|
|
||||||
@define-color sidebar_bg_color #{sidebar_bg};
|
@define-color sidebar_bg_color {sidebar_bg};
|
||||||
@define-color sidebar_fg_color #{sidebar_fg};
|
@define-color sidebar_fg_color {sidebar_fg};
|
||||||
@define-color sidebar_shade_color #{sidebar_shade};
|
@define-color sidebar_shade_color {sidebar_shade};
|
||||||
@define-color sidebar_backdrop_color #{sidebar_backdrop};
|
@define-color sidebar_backdrop_color {sidebar_backdrop};
|
||||||
|
|
||||||
@define-color secondary_sidebar_bg_color #{secondary_sidebar_bg};
|
@define-color secondary_sidebar_bg_color {secondary_sidebar_bg};
|
||||||
@define-color secondary_sidebar_fg_color #{secondary_sidebar_fg};
|
@define-color secondary_sidebar_fg_color {secondary_sidebar_fg};
|
||||||
@define-color secondary_sidebar_shade_color #{secondary_sidebar_shade};
|
@define-color secondary_sidebar_shade_color {secondary_sidebar_shade};
|
||||||
@define-color secondary_sidebar_backdrop_color #{secondary_sidebar_backdrop};
|
@define-color secondary_sidebar_backdrop_color {secondary_sidebar_backdrop};
|
||||||
|
|
||||||
@define-color card_bg_color #{card_bg};
|
@define-color card_bg_color {card_bg};
|
||||||
@define-color card_fg_color #{card_fg};
|
@define-color card_fg_color {card_fg};
|
||||||
|
|
||||||
@define-color thumbnail_bg_color #{thumbnail_bg};
|
@define-color thumbnail_bg_color {thumbnail_bg};
|
||||||
@define-color thumbnail_fg_color #{thumbnail_fg};
|
@define-color thumbnail_fg_color {thumbnail_fg};
|
||||||
|
|
||||||
@define-color dialog_bg_color #{dialog_bg};
|
@define-color dialog_bg_color {dialog_bg};
|
||||||
@define-color dialog_fg_color #{dialog_fg};
|
@define-color dialog_fg_color {dialog_fg};
|
||||||
|
|
||||||
@define-color popover_bg_color #{popover_bg};
|
@define-color popover_bg_color {popover_bg};
|
||||||
@define-color popover_fg_color #{popover_fg};
|
@define-color popover_fg_color {popover_fg};
|
||||||
|
|
||||||
@define-color shade_color #{shade};
|
@define-color shade_color {shade};
|
||||||
@define-color scrollbar_outline_color #{scrollbar_outline};
|
@define-color scrollbar_outline_color {scrollbar_outline};
|
||||||
|
|
||||||
|
.close, .maximize, .minimize {{
|
||||||
|
background: transparent;
|
||||||
|
}}
|
||||||
|
.close:not(:hover) > image,
|
||||||
|
.maximize:not(:hover) > image,
|
||||||
|
.minimize:not(:hover) > image {{
|
||||||
|
background: transparent;
|
||||||
|
}}
|
||||||
|
.close > image,
|
||||||
|
.maximize > image,
|
||||||
|
.minimize > image {{
|
||||||
|
transition: 0;
|
||||||
|
color: @accent_bg_color;
|
||||||
|
border-radius: 100%;
|
||||||
|
}}
|
||||||
|
.close:backdrop > image,
|
||||||
|
.maximize:backdrop > image,
|
||||||
|
.minimize:backdrop > image {{
|
||||||
|
filter: grayscale(1) brightness(1.7);
|
||||||
|
}}
|
||||||
|
.close:hover > image,
|
||||||
|
.maximize:hover > image,
|
||||||
|
.minimize:hover > image {{
|
||||||
|
background: {window_control_hover_bg};
|
||||||
|
}}
|
||||||
|
.image-button {{
|
||||||
|
transition: 0;
|
||||||
|
}}
|
||||||
|
.image-button > image,
|
||||||
|
.image-button > box,
|
||||||
|
.popup > image,
|
||||||
|
.toggle > image,
|
||||||
|
.toggle > arrow,
|
||||||
|
.toggle > box > contents > image,
|
||||||
|
button > image,
|
||||||
|
button > widget > box > image,
|
||||||
|
stack > box > image
|
||||||
|
{{
|
||||||
|
color: @accent_bg_color;
|
||||||
|
}}
|
||||||
|
.image-button:insensitive > image,
|
||||||
|
.image-button:insensitive > box,
|
||||||
|
.popup:insensitive > image,
|
||||||
|
.toggle:insensitive > image,
|
||||||
|
.toggle:insensitive > box > contents > image,
|
||||||
|
button:insensitive > image,
|
||||||
|
button:insensitive > widget > box > image,
|
||||||
|
stack:insensitive > box > image {{
|
||||||
|
opacity: 0.5;
|
||||||
|
}}
|
||||||
|
.image-button:backdrop > image,
|
||||||
|
.image-button:backdrop > box,
|
||||||
|
.popup:backdrop > image,
|
||||||
|
.toggle:backdrop > image,
|
||||||
|
.toggle:backdrop > arrow,
|
||||||
|
button:backdrop > image,
|
||||||
|
button:backdrop > widget > box > image,
|
||||||
|
stack:backdrop > box > image,
|
||||||
|
.toggle:backdrop > box > contents > image {{
|
||||||
|
filter: grayscale(1) brightness(1.7);
|
||||||
|
}}
|
||||||
|
window {{ background: @window_bg_color }}
|
||||||
|
window:not(.maximized) {{
|
||||||
|
border-radius: {corner_radius:.0}px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
window, statuspage.view {{ background: @window_bg_color }}
|
||||||
"#};
|
"#};
|
||||||
|
|
||||||
css.push_str(&component_gtk4_css("accent", accent));
|
css.push_str(&component_gtk4_css("accent", accent));
|
||||||
|
|
@ -130,10 +204,10 @@ impl Theme {
|
||||||
css.push_str(&color_css("purple", palette.ext_purple));
|
css.push_str(&color_css("purple", palette.ext_purple));
|
||||||
let neutral_steps = steps(palette.neutral_5, NonZeroUsize::new(10).unwrap());
|
let neutral_steps = steps(palette.neutral_5, NonZeroUsize::new(10).unwrap());
|
||||||
for (i, c) in neutral_steps[..5].iter().enumerate() {
|
for (i, c) in neutral_steps[..5].iter().enumerate() {
|
||||||
css.push_str(&format!("@define-color light_{i} #{};\n", to_hex(*c)));
|
css.push_str(&format!("@define-color light_{i} {};\n", to_rgba(*c)));
|
||||||
}
|
}
|
||||||
for (i, c) in neutral_steps[5..].iter().enumerate() {
|
for (i, c) in neutral_steps[5..].iter().enumerate() {
|
||||||
css.push_str(&format!("@define-color dark_{i} #{};\n", to_hex(*c)));
|
css.push_str(&format!("@define-color dark_{i} {};\n", to_rgba(*c)));
|
||||||
}
|
}
|
||||||
css
|
css
|
||||||
}
|
}
|
||||||
|
|
@ -232,13 +306,13 @@ impl Theme {
|
||||||
fn component_gtk4_css(prefix: &str, c: &Component) -> String {
|
fn component_gtk4_css(prefix: &str, c: &Component) -> String {
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
@define-color {prefix}_color #{};
|
@define-color {prefix}_color {};
|
||||||
@define-color {prefix}_bg_color #{};
|
@define-color {prefix}_bg_color {};
|
||||||
@define-color {prefix}_fg_color #{};
|
@define-color {prefix}_fg_color {};
|
||||||
"#,
|
"#,
|
||||||
to_hex(c.base),
|
to_rgba(c.base),
|
||||||
to_hex(c.base),
|
to_rgba(c.base),
|
||||||
to_hex(c.on),
|
to_rgba(c.on),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,17 +322,17 @@ fn color_css(prefix: &str, c_3: Srgba) -> String {
|
||||||
let c_1: Srgba = oklch.lighten(0.2).into_color();
|
let c_1: Srgba = oklch.lighten(0.2).into_color();
|
||||||
let c_4: Srgba = oklch.darken(0.1).into_color();
|
let c_4: Srgba = oklch.darken(0.1).into_color();
|
||||||
let c_5: Srgba = oklch.darken(0.2).into_color();
|
let c_5: Srgba = oklch.darken(0.2).into_color();
|
||||||
let c_1 = to_hex(c_1);
|
let c_1 = to_rgba(c_1);
|
||||||
let c_2 = to_hex(c_2);
|
let c_2 = to_rgba(c_2);
|
||||||
let c_3 = to_hex(c_3);
|
let c_3 = to_rgba(c_3);
|
||||||
let c_4 = to_hex(c_4);
|
let c_4 = to_rgba(c_4);
|
||||||
let c_5 = to_hex(c_5);
|
let c_5 = to_rgba(c_5);
|
||||||
|
|
||||||
format! {r#"
|
format! {r#"
|
||||||
@define-color {prefix}_1 #{c_1};
|
@define-color {prefix}_1 {c_1};
|
||||||
@define-color {prefix}_2 #{c_2};
|
@define-color {prefix}_2 {c_2};
|
||||||
@define-color {prefix}_3 #{c_3};
|
@define-color {prefix}_3 {c_3};
|
||||||
@define-color {prefix}_4 #{c_4};
|
@define-color {prefix}_4 {c_4};
|
||||||
@define-color {prefix}_5 #{c_5};
|
@define-color {prefix}_5 {c_5};
|
||||||
"#}
|
"#}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,5 +42,16 @@ impl Theme {
|
||||||
|
|
||||||
pub fn to_hex(c: Srgba) -> String {
|
pub fn to_hex(c: Srgba) -> String {
|
||||||
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
|
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
|
||||||
format!("{:02x}{:02x}{:02x}", c_u8.red, c_u8.green, c_u8.blue)
|
format!(
|
||||||
|
"{:02x}{:02x}{:02x}{:02x}",
|
||||||
|
c_u8.red, c_u8.green, c_u8.blue, c_u8.alpha
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_rgba(c: Srgba) -> String {
|
||||||
|
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
|
||||||
|
format!(
|
||||||
|
"rgba({}, {}, {}, {:1.2})",
|
||||||
|
c_u8.red, c_u8.green, c_u8.blue, c.alpha
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 493e17a0105c7523fb7ff5fd7221ec586ac9010f
|
|
||||||
|
|
@ -14,9 +14,7 @@ use cosmic::iced_core::{Length, Size};
|
||||||
use cosmic::widget::menu::action::MenuAction;
|
use cosmic::widget::menu::action::MenuAction;
|
||||||
use cosmic::widget::menu::key_bind::KeyBind;
|
use cosmic::widget::menu::key_bind::KeyBind;
|
||||||
use cosmic::widget::menu::key_bind::Modifier;
|
use cosmic::widget::menu::key_bind::Modifier;
|
||||||
use cosmic::widget::menu::menu_tree::{menu_items, menu_root, MenuItem};
|
use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
|
||||||
use cosmic::widget::menu::{ItemHeight, ItemWidth, MenuBar, MenuTree};
|
|
||||||
use cosmic::widget::segmented_button::Entity;
|
|
||||||
use cosmic::{executor, Element};
|
use cosmic::{executor, Element};
|
||||||
|
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
|
|
@ -67,7 +65,7 @@ pub enum Action {
|
||||||
|
|
||||||
impl MenuAction for Action {
|
impl MenuAction for Action {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
fn message(&self, _entity_opt: Option<Entity>) -> Self::Message {
|
fn message(&self) -> Self::Message {
|
||||||
match self {
|
match self {
|
||||||
Action::WindowClose => Message::WindowClose,
|
Action::WindowClose => Message::WindowClose,
|
||||||
Action::ToggleHideContent => Message::ToggleHideContent,
|
Action::ToggleHideContent => Message::ToggleHideContent,
|
||||||
|
|
@ -156,23 +154,23 @@ impl cosmic::Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
|
||||||
MenuBar::new(vec![MenuTree::with_children(
|
menu::bar(vec![menu::Tree::with_children(
|
||||||
menu_root("File"),
|
menu::root("File"),
|
||||||
menu_items(
|
menu::items(
|
||||||
key_binds,
|
key_binds,
|
||||||
vec![
|
vec![
|
||||||
MenuItem::Button("New window", Action::WindowNew),
|
menu::Item::Button("New window", Action::WindowNew),
|
||||||
MenuItem::Divider,
|
menu::Item::Divider,
|
||||||
MenuItem::Folder(
|
menu::Item::Folder(
|
||||||
"View",
|
"View",
|
||||||
vec![MenuItem::CheckBox(
|
vec![menu::Item::CheckBox(
|
||||||
"Hide content",
|
"Hide content",
|
||||||
config.hide_content,
|
config.hide_content,
|
||||||
Action::ToggleHideContent,
|
Action::ToggleHideContent,
|
||||||
)],
|
)],
|
||||||
),
|
),
|
||||||
MenuItem::Divider,
|
menu::Item::Divider,
|
||||||
MenuItem::Button("Quit", Action::WindowClose),
|
menu::Item::Button("Quit", Action::WindowClose),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)])
|
)])
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,7 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
// Send this back to the application.
|
// Send this back to the application.
|
||||||
Message::FileRead(url, contents)
|
Message::FileRead(url, contents)
|
||||||
})
|
});
|
||||||
.map(cosmic::app::message::app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new open dialog.
|
// Creates a new open dialog.
|
||||||
|
|
@ -167,8 +166,7 @@ impl cosmic::Application for App {
|
||||||
|
|
||||||
Err(why) => Message::OpenError(Arc::new(why)),
|
Err(why) => Message::OpenError(Arc::new(why)),
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.map(cosmic::app::Message::App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Displays an error in the application's warning bar.
|
// Displays an error in the application's warning bar.
|
||||||
|
|
|
||||||
2
iced
2
iced
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7c58b8eda9885083e019a5ea14edf4652740e154
|
Subproject commit 2dcae59258c9cee6862c7fb2193066a1cb1e30c2
|
||||||
2
justfile
2
justfile
|
|
@ -1,4 +1,4 @@
|
||||||
examples := 'applet application config cosmic cosmic-design-demo multi-window open-dialog'
|
examples := 'applet application calendar config context-menu cosmic image-button menu multi-window nav-context open-dialog'
|
||||||
clippy_args := '-W clippy::all -W clippy::pedantic'
|
clippy_args := '-W clippy::all -W clippy::pedantic'
|
||||||
|
|
||||||
# Check for errors and linter warnings
|
# Check for errors and linter warnings
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,6 @@ pub struct Core {
|
||||||
/// Configured theme mode
|
/// Configured theme mode
|
||||||
pub(super) system_theme_mode: ThemeMode,
|
pub(super) system_theme_mode: ThemeMode,
|
||||||
|
|
||||||
/// Libcosmic toolkit configuration.
|
|
||||||
pub(super) toolkit_config: CosmicTk,
|
|
||||||
|
|
||||||
pub(super) portal_is_dark: Option<bool>,
|
pub(super) portal_is_dark: Option<bool>,
|
||||||
|
|
||||||
pub(super) portal_accent: Option<Srgba>,
|
pub(super) portal_accent: Option<Srgba>,
|
||||||
|
|
@ -107,7 +104,7 @@ impl Default for Core {
|
||||||
active: true,
|
active: true,
|
||||||
context_id: crate::widget::nav_bar::Id::null(),
|
context_id: crate::widget::nav_bar::Id::null(),
|
||||||
toggled: true,
|
toggled: true,
|
||||||
toggled_condensed: true,
|
toggled_condensed: false,
|
||||||
},
|
},
|
||||||
scale_factor: 1.0,
|
scale_factor: 1.0,
|
||||||
title: HashMap::new(),
|
title: HashMap::new(),
|
||||||
|
|
@ -123,16 +120,6 @@ impl Default for Core {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
toolkit_config: CosmicTk::config()
|
|
||||||
.map(|c| {
|
|
||||||
CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| {
|
|
||||||
for why in errors {
|
|
||||||
tracing::error!(?why, "CosmicTk config entry error");
|
|
||||||
}
|
|
||||||
mode
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
window: Window {
|
window: Window {
|
||||||
context_title: String::new(),
|
context_title: String::new(),
|
||||||
header_title: String::new(),
|
header_title: String::new(),
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn subscription(&self) -> Subscription<Self::Message> {
|
fn subscription(&self) -> Subscription<Self::Message> {
|
||||||
let window_events = listen_with(|event, _| {
|
let window_events = listen_with(|event, _| {
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -197,7 +198,7 @@ where
|
||||||
self.app.subscription().map(super::Message::App),
|
self.app.subscription().map(super::Message::App),
|
||||||
self.app
|
self.app
|
||||||
.core()
|
.core()
|
||||||
.watch_config::<crate::config::CosmicTk>(crate::config::toolkit::ID)
|
.watch_config::<crate::config::CosmicTk>(crate::config::ID)
|
||||||
.map(|update| {
|
.map(|update| {
|
||||||
for why in update.errors {
|
for why in update.errors {
|
||||||
tracing::error!(?why, "cosmic toolkit config update error");
|
tracing::error!(?why, "cosmic toolkit config update error");
|
||||||
|
|
@ -408,11 +409,9 @@ impl<T: Application> Cosmic<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::AppThemeChange(mut theme) => {
|
Message::AppThemeChange(mut theme) => {
|
||||||
// Apply last-known system theme if the system theme is preferred.
|
|
||||||
if let ThemeType::System { theme: _, .. } = theme.theme_type {
|
if let ThemeType::System { theme: _, .. } = theme.theme_type {
|
||||||
self.app.core_mut().theme_sub_counter += 1;
|
self.app.core_mut().theme_sub_counter += 1;
|
||||||
|
|
||||||
theme = self.app.core().system_theme.clone();
|
|
||||||
let portal_accent = self.app.core().portal_accent;
|
let portal_accent = self.app.core().portal_accent;
|
||||||
if let Some(a) = portal_accent {
|
if let Some(a) = portal_accent {
|
||||||
let t_inner = theme.cosmic();
|
let t_inner = theme.cosmic();
|
||||||
|
|
@ -429,6 +428,11 @@ impl<T: Application> Cosmic<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::SystemThemeChange(keys, theme) => {
|
Message::SystemThemeChange(keys, theme) => {
|
||||||
|
let cur_is_dark = THEME.with(|t| t.borrow().theme_type.is_dark());
|
||||||
|
// Ignore updates if the current theme mode does not match.
|
||||||
|
if cur_is_dark != theme.cosmic().is_dark {
|
||||||
|
return iced::Command::none();
|
||||||
|
}
|
||||||
let cmd = self.app.system_theme_update(&keys, theme.cosmic());
|
let cmd = self.app.system_theme_update(&keys, theme.cosmic());
|
||||||
// Record the last-known system theme in event that the current theme is custom.
|
// Record the last-known system theme in event that the current theme is custom.
|
||||||
self.app.core_mut().system_theme = theme.clone();
|
self.app.core_mut().system_theme = theme.clone();
|
||||||
|
|
@ -472,6 +476,9 @@ impl<T: Application> Cosmic<T> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Message::SystemThemeModeChange(keys, mode) => {
|
Message::SystemThemeModeChange(keys, mode) => {
|
||||||
|
if !keys.contains(&"is_dark") {
|
||||||
|
return iced::Command::none();
|
||||||
|
}
|
||||||
if THEME.with(|t| match t.borrow().theme_type {
|
if THEME.with(|t| match t.borrow().theme_type {
|
||||||
ThemeType::System {
|
ThemeType::System {
|
||||||
theme: _,
|
theme: _,
|
||||||
|
|
@ -619,7 +626,7 @@ impl<T: Application> Cosmic<T> {
|
||||||
crate::icon_theme::set_default(config.icon_theme.clone());
|
crate::icon_theme::set_default(config.icon_theme.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.app.core_mut().toolkit_config = config;
|
crate::config::COSMIC_TK.with(|tk| *tk.borrow_mut() = config);
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Focus(f) => {
|
Message::Focus(f) => {
|
||||||
|
|
|
||||||
143
src/app/mod.rs
143
src/app/mod.rs
|
|
@ -37,6 +37,12 @@ pub mod message {
|
||||||
pub const fn none<M>() -> Message<M> {
|
pub const fn none<M>() -> Message<M> {
|
||||||
Message::None
|
Message::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M> From<M> for Message<M> {
|
||||||
|
fn from(value: M) -> Self {
|
||||||
|
Self::App(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::command::Command;
|
pub use self::command::Command;
|
||||||
|
|
@ -45,7 +51,7 @@ pub use self::settings::Settings;
|
||||||
use crate::config::CosmicTk;
|
use crate::config::CosmicTk;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::theme::THEME;
|
use crate::theme::THEME;
|
||||||
use crate::widget::{context_drawer, menu, nav_bar, popover};
|
use crate::widget::{context_drawer, id_container, menu, nav_bar, popover};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use iced::Subscription;
|
use iced::Subscription;
|
||||||
#[cfg(all(feature = "winit", feature = "multi-window"))]
|
#[cfg(all(feature = "winit", feature = "multi-window"))]
|
||||||
|
|
@ -78,7 +84,7 @@ pub(crate) fn iced_settings<App: Application>(
|
||||||
if let Some(icon_theme) = settings.default_icon_theme {
|
if let Some(icon_theme) = settings.default_icon_theme {
|
||||||
crate::icon_theme::set_default(icon_theme);
|
crate::icon_theme::set_default(icon_theme);
|
||||||
} else {
|
} else {
|
||||||
crate::icon_theme::set_default(core.toolkit_config.icon_theme.clone());
|
crate::icon_theme::set_default(crate::config::icon_theme());
|
||||||
}
|
}
|
||||||
|
|
||||||
THEME.with(move |t| {
|
THEME.with(move |t| {
|
||||||
|
|
@ -92,7 +98,11 @@ pub(crate) fn iced_settings<App: Application>(
|
||||||
iced.default_font = settings.default_font;
|
iced.default_font = settings.default_font;
|
||||||
iced.default_text_size = iced::Pixels(settings.default_text_size);
|
iced.default_text_size = iced::Pixels(settings.default_text_size);
|
||||||
iced.exit_on_close_request = settings.exit_on_close;
|
iced.exit_on_close_request = settings.exit_on_close;
|
||||||
// iced.window.exit_on_close_request = settings.exit_on_close;
|
#[cfg(not(feature = "wayland"))]
|
||||||
|
{
|
||||||
|
let exit_on_close = settings.exit_on_close;
|
||||||
|
iced.window.exit_on_close_request = exit_on_close;
|
||||||
|
}
|
||||||
iced.id = Some(App::APP_ID.to_owned());
|
iced.id = Some(App::APP_ID.to_owned());
|
||||||
#[cfg(all(not(feature = "wayland"), target_os = "linux"))]
|
#[cfg(all(not(feature = "wayland"), target_os = "linux"))]
|
||||||
{
|
{
|
||||||
|
|
@ -149,40 +159,11 @@ pub(crate) fn iced_settings<App: Application>(
|
||||||
///
|
///
|
||||||
/// Returns error on application failure.
|
/// Returns error on application failure.
|
||||||
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
|
pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
wgpu_power_pref();
|
|
||||||
|
|
||||||
let settings = iced_settings::<App>(settings, flags);
|
let settings = iced_settings::<App>(settings, flags);
|
||||||
|
|
||||||
cosmic::Cosmic::<App>::run(settings)
|
cosmic::Cosmic::<App>::run(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default to rendering the application with the low power GPU preference.
|
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
fn wgpu_power_pref() {
|
|
||||||
fn is_desktop() -> bool {
|
|
||||||
let chassis = std::fs::read_to_string("/sys/class/dmi/id/chassis_type").unwrap_or_default();
|
|
||||||
|
|
||||||
chassis.trim() == "3"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore if the system is a desktop.
|
|
||||||
if is_desktop() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore if requested to run on NVIDIA GPU
|
|
||||||
if std::env::var("__NV_PRIME_RENDER_OFFLOAD").ok().as_deref() == Some("1") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::items_after_statements)]
|
|
||||||
const VAR: &str = "WGPU_POWER_PREF";
|
|
||||||
if std::env::var(VAR).is_err() {
|
|
||||||
std::env::set_var(VAR, "low");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "single-instance")]
|
#[cfg(feature = "single-instance")]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DbusActivationMessage<Action = String, Args = Vec<String>> {
|
pub struct DbusActivationMessage<Action = String, Args = Vec<String>> {
|
||||||
|
|
@ -429,7 +410,7 @@ where
|
||||||
fn core_mut(&mut self) -> &mut Core;
|
fn core_mut(&mut self) -> &mut Core;
|
||||||
|
|
||||||
/// Creates the application, and optionally emits command on initialize.
|
/// Creates the application, and optionally emits command on initialize.
|
||||||
fn init(core: Core, flags: Self::Flags) -> (Self, iced::Command<Message<Self::Message>>);
|
fn init(core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>);
|
||||||
|
|
||||||
/// Displays a context drawer on the side of the application window when `Some`.
|
/// Displays a context drawer on the side of the application window when `Some`.
|
||||||
fn context_drawer(&self) -> Option<Element<Self::Message>> {
|
fn context_drawer(&self) -> Option<Element<Self::Message>> {
|
||||||
|
|
@ -506,28 +487,28 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when context drawer is toggled
|
// Called when context drawer is toggled
|
||||||
fn on_context_drawer(&mut self) -> iced::Command<Message<Self::Message>> {
|
fn on_context_drawer(&mut self) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the escape key is pressed.
|
/// Called when the escape key is pressed.
|
||||||
fn on_escape(&mut self) -> iced::Command<Message<Self::Message>> {
|
fn on_escape(&mut self) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a navigation item is selected.
|
/// Called when a navigation item is selected.
|
||||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> iced::Command<Message<Self::Message>> {
|
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a context menu is requested for a navigation item.
|
/// Called when a context menu is requested for a navigation item.
|
||||||
fn on_nav_context(&mut self, id: nav_bar::Id) -> iced::Command<Message<Self::Message>> {
|
fn on_nav_context(&mut self, id: nav_bar::Id) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the search function is requested.
|
/// Called when the search function is requested.
|
||||||
fn on_search(&mut self) -> iced::Command<Message<Self::Message>> {
|
fn on_search(&mut self) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a window is resized.
|
/// Called when a window is resized.
|
||||||
|
|
@ -539,8 +520,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Respond to an application-specific message.
|
/// Respond to an application-specific message.
|
||||||
fn update(&mut self, message: Self::Message) -> iced::Command<Message<Self::Message>> {
|
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Respond to a system theme change
|
/// Respond to a system theme change
|
||||||
|
|
@ -548,8 +529,8 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
keys: &[&'static str],
|
keys: &[&'static str],
|
||||||
new_theme: &cosmic_theme::Theme,
|
new_theme: &cosmic_theme::Theme,
|
||||||
) -> iced::Command<Message<Self::Message>> {
|
) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Respond to a system theme mode change
|
/// Respond to a system theme mode change
|
||||||
|
|
@ -557,8 +538,8 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
keys: &[&'static str],
|
keys: &[&'static str],
|
||||||
new_theme: &cosmic_theme::ThemeMode,
|
new_theme: &cosmic_theme::ThemeMode,
|
||||||
) -> iced::Command<Message<Self::Message>> {
|
) -> Command<Self::Message> {
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs the view for the main window.
|
/// Constructs the view for the main window.
|
||||||
|
|
@ -576,24 +557,21 @@ where
|
||||||
|
|
||||||
/// Handles dbus activation messages
|
/// Handles dbus activation messages
|
||||||
#[cfg(feature = "single-instance")]
|
#[cfg(feature = "single-instance")]
|
||||||
fn dbus_activation(
|
fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Command<Self::Message> {
|
||||||
&mut self,
|
Command::none()
|
||||||
msg: DbusActivationMessage,
|
|
||||||
) -> iced::Command<Message<Self::Message>> {
|
|
||||||
iced::Command::none()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Methods automatically derived for all types implementing [`Application`].
|
/// Methods automatically derived for all types implementing [`Application`].
|
||||||
pub trait ApplicationExt: Application {
|
pub trait ApplicationExt: Application {
|
||||||
/// Initiates a window drag.
|
/// Initiates a window drag.
|
||||||
fn drag(&mut self) -> iced::Command<Message<Self::Message>>;
|
fn drag(&mut self) -> Command<Self::Message>;
|
||||||
|
|
||||||
/// Maximizes the window.
|
/// Maximizes the window.
|
||||||
fn maximize(&mut self) -> iced::Command<Message<Self::Message>>;
|
fn maximize(&mut self) -> Command<Self::Message>;
|
||||||
|
|
||||||
/// Minimizes the window.
|
/// Minimizes the window.
|
||||||
fn minimize(&mut self) -> iced::Command<Message<Self::Message>>;
|
fn minimize(&mut self) -> Command<Self::Message>;
|
||||||
/// Get the title of the main window.
|
/// Get the title of the main window.
|
||||||
|
|
||||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||||
|
|
@ -615,30 +593,26 @@ pub trait ApplicationExt: Application {
|
||||||
|
|
||||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||||
/// Set the title of the main window.
|
/// Set the title of the main window.
|
||||||
fn set_window_title(&mut self, title: String) -> iced::Command<Message<Self::Message>>;
|
fn set_window_title(&mut self, title: String) -> Command<Self::Message>;
|
||||||
|
|
||||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||||
/// Set the title of a window.
|
/// Set the title of a window.
|
||||||
fn set_window_title(
|
fn set_window_title(&mut self, title: String, id: window::Id) -> Command<Self::Message>;
|
||||||
&mut self,
|
|
||||||
title: String,
|
|
||||||
id: window::Id,
|
|
||||||
) -> iced::Command<Message<Self::Message>>;
|
|
||||||
|
|
||||||
/// View template for the main window.
|
/// View template for the main window.
|
||||||
fn view_main(&self) -> Element<Message<Self::Message>>;
|
fn view_main(&self) -> Element<Message<Self::Message>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<App: Application> ApplicationExt for App {
|
impl<App: Application> ApplicationExt for App {
|
||||||
fn drag(&mut self) -> iced::Command<Message<Self::Message>> {
|
fn drag(&mut self) -> Command<Self::Message> {
|
||||||
command::drag(Some(self.main_window_id()))
|
command::drag(Some(self.main_window_id()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maximize(&mut self) -> iced::Command<Message<Self::Message>> {
|
fn maximize(&mut self) -> Command<Self::Message> {
|
||||||
command::maximize(Some(self.main_window_id()), true)
|
command::maximize(Some(self.main_window_id()), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn minimize(&mut self) -> iced::Command<Message<Self::Message>> {
|
fn minimize(&mut self) -> Command<Self::Message> {
|
||||||
command::minimize(Some(self.main_window_id()))
|
command::minimize(Some(self.main_window_id()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -656,23 +630,20 @@ impl<App: Application> ApplicationExt for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||||
fn set_window_title(
|
fn set_window_title(&mut self, title: String, id: window::Id) -> Command<Self::Message> {
|
||||||
&mut self,
|
|
||||||
title: String,
|
|
||||||
id: window::Id,
|
|
||||||
) -> iced::Command<Message<Self::Message>> {
|
|
||||||
self.core_mut().title.insert(id, title.clone());
|
self.core_mut().title.insert(id, title.clone());
|
||||||
command::set_title(Some(id), title)
|
command::set_title(Some(id), title)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||||
fn set_window_title(&mut self, title: String) -> iced::Command<Message<Self::Message>> {
|
fn set_window_title(&mut self, title: String) -> Command<Self::Message> {
|
||||||
let id = self.main_window_id();
|
let id = self.main_window_id();
|
||||||
|
|
||||||
self.core_mut().title.insert(id, title.clone());
|
self.core_mut().title.insert(id, title.clone());
|
||||||
iced::Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
/// Creates the view for the main window.
|
/// Creates the view for the main window.
|
||||||
fn view_main(&self) -> Element<Message<Self::Message>> {
|
fn view_main(&self) -> Element<Message<Self::Message>> {
|
||||||
let core = self.core();
|
let core = self.core();
|
||||||
|
|
@ -685,8 +656,11 @@ impl<App: Application> ApplicationExt for App {
|
||||||
let mut widgets = Vec::with_capacity(2);
|
let mut widgets = Vec::with_capacity(2);
|
||||||
|
|
||||||
// Insert nav bar onto the left side of the window.
|
// Insert nav bar onto the left side of the window.
|
||||||
if let Some(nav) = self.nav_bar() {
|
if let Some(nav) = self
|
||||||
widgets.push(nav);
|
.nav_bar()
|
||||||
|
.map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar")))
|
||||||
|
{
|
||||||
|
widgets.push(nav.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.nav_model().is_none() || core.show_content() {
|
if self.nav_model().is_none() || core.show_content() {
|
||||||
|
|
@ -699,7 +673,12 @@ impl<App: Application> ApplicationExt for App {
|
||||||
main_content,
|
main_content,
|
||||||
context.map(Message::App),
|
context.map(Message::App),
|
||||||
)
|
)
|
||||||
.into()
|
.apply(|drawer| {
|
||||||
|
Element::from(id_container(
|
||||||
|
drawer,
|
||||||
|
iced_core::id::Id::new("COSMIC_context_drawer"),
|
||||||
|
))
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
main_content
|
main_content
|
||||||
});
|
});
|
||||||
|
|
@ -715,6 +694,7 @@ impl<App: Application> ApplicationExt for App {
|
||||||
.width(iced::Length::Fill)
|
.width(iced::Length::Fill)
|
||||||
.height(iced::Length::Fill)
|
.height(iced::Length::Fill)
|
||||||
.style(crate::theme::Container::WindowBackground)
|
.style(crate::theme::Container::WindowBackground)
|
||||||
|
.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container")))
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
content_row.into()
|
content_row.into()
|
||||||
|
|
@ -744,11 +724,11 @@ impl<App: Application> ApplicationExt for App {
|
||||||
header = header.start(toggle);
|
header = header.start(toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if core.window.show_maximize && core.toolkit_config.show_maximize {
|
if core.window.show_maximize && crate::config::show_maximize() {
|
||||||
header = header.on_maximize(Message::Cosmic(cosmic::Message::Maximize));
|
header = header.on_maximize(Message::Cosmic(cosmic::Message::Maximize));
|
||||||
}
|
}
|
||||||
|
|
||||||
if core.window.show_minimize && core.toolkit_config.show_minimize {
|
if core.window.show_minimize && crate::config::show_minimize() {
|
||||||
header = header.on_minimize(Message::Cosmic(cosmic::Message::Minimize));
|
header = header.on_minimize(Message::Cosmic(cosmic::Message::Minimize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -764,7 +744,7 @@ impl<App: Application> ApplicationExt for App {
|
||||||
header = header.end(element.map(Message::App));
|
header = header.end(element.map(Message::App));
|
||||||
}
|
}
|
||||||
|
|
||||||
header
|
header.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header")))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -775,7 +755,10 @@ impl<App: Application> ApplicationExt for App {
|
||||||
// Show any current dialog on top and centered over the view content
|
// Show any current dialog on top and centered over the view content
|
||||||
// We have to use a popover even without a dialog to keep the tree from changing
|
// We have to use a popover even without a dialog to keep the tree from changing
|
||||||
let mut popover = popover(view_column).modal(true);
|
let mut popover = popover(view_column).modal(true);
|
||||||
if let Some(dialog) = self.dialog() {
|
if let Some(dialog) = self
|
||||||
|
.dialog()
|
||||||
|
.map(|w| Element::from(id_container(w, iced_core::id::Id::new("COSMIC_dialog"))))
|
||||||
|
{
|
||||||
popover = popover.popup(dialog.map(Message::App));
|
popover = popover.popup(dialog.map(Message::App));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -372,7 +372,7 @@ pub fn style() -> <crate::Theme as iced_style::application::StyleSheet>::Style {
|
||||||
pub fn menu_button<'a, Message>(
|
pub fn menu_button<'a, Message>(
|
||||||
content: impl Into<Element<'a, Message>>,
|
content: impl Into<Element<'a, Message>>,
|
||||||
) -> crate::widget::Button<'a, Message> {
|
) -> crate::widget::Button<'a, Message> {
|
||||||
crate::widget::Button::new(content)
|
crate::widget::button(content)
|
||||||
.style(Button::AppletMenu)
|
.style(Button::AppletMenu)
|
||||||
.padding(menu_control_padding())
|
.padding(menu_control_padding())
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,20 @@ use iced_runtime::command::Action;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
/// Yields a command which contains a batch of commands.
|
/// Yields a command which contains a batch of commands.
|
||||||
pub fn batch<M>(commands: impl IntoIterator<Item = Command<M>>) -> Command<M> {
|
pub fn batch<X: 'static + Into<Y>, Y: 'static>(
|
||||||
Command::batch(commands)
|
commands: impl IntoIterator<Item = Command<X>>,
|
||||||
|
) -> Command<Y> {
|
||||||
|
Command::batch(commands).map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Yields a command which will run the future on the runtime executor.
|
/// Yields a command which will run the future on the runtime executor.
|
||||||
pub fn future<M>(future: impl Future<Output = M> + Send + 'static) -> Command<M> {
|
pub fn future<X: Into<Y>, Y>(future: impl Future<Output = X> + Send + 'static) -> Command<Y> {
|
||||||
Command::single(Action::Future(Box::pin(future)))
|
Command::single(Action::Future(Box::pin(async move { future.await.into() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Yields a command which will return a message.
|
/// Yields a command which will return a message.
|
||||||
pub fn message<M: Send + 'static>(message: M) -> Command<M> {
|
pub fn message<X: Send + 'static + Into<Y>, Y>(message: X) -> Command<Y> {
|
||||||
future(async move { message })
|
future(async move { message.into() })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initiates a window drag.
|
/// Initiates a window drag.
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,102 @@
|
||||||
|
|
||||||
//! Configurations available to libcosmic applications.
|
//! Configurations available to libcosmic applications.
|
||||||
|
|
||||||
pub mod toolkit;
|
use cosmic_config::cosmic_config_derive::CosmicConfigEntry;
|
||||||
pub use toolkit::CosmicTk;
|
use cosmic_config::{Config, CosmicConfigEntry};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// ID for the `CosmicTk` config.
|
||||||
|
pub const ID: &str = "com.system76.CosmicTk";
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static COSMIC_TK: RefCell<CosmicTk> = RefCell::new(CosmicTk::config()
|
||||||
|
.map(|c| {
|
||||||
|
CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| {
|
||||||
|
for why in errors {
|
||||||
|
tracing::error!(?why, "CosmicTk config entry error");
|
||||||
|
}
|
||||||
|
mode
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply the theme to other toolkits.
|
||||||
|
pub fn apply_theme_global() -> bool {
|
||||||
|
COSMIC_TK.with(|tk| tk.borrow().apply_theme_global)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show minimize button in window header.
|
||||||
|
pub fn show_minimize() -> bool {
|
||||||
|
COSMIC_TK.with(|tk| tk.borrow().show_minimize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show maximize button in window header.
|
||||||
|
pub fn show_maximize() -> bool {
|
||||||
|
COSMIC_TK.with(|tk| tk.borrow().show_maximize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preferred icon theme.
|
||||||
|
pub fn icon_theme() -> String {
|
||||||
|
COSMIC_TK.with(|tk| tk.borrow().icon_theme.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Density of CSD/SSD header bars.
|
||||||
|
pub fn header_size() -> Density {
|
||||||
|
COSMIC_TK.with(|tk| tk.borrow().header_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface density.
|
||||||
|
pub fn interface_density() -> Density {
|
||||||
|
COSMIC_TK.with(|tk| tk.borrow().interface_density)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, CosmicConfigEntry, Debug, Eq, PartialEq)]
|
||||||
|
#[version = 1]
|
||||||
|
pub struct CosmicTk {
|
||||||
|
/// Apply the theme to other toolkits.
|
||||||
|
pub apply_theme_global: bool,
|
||||||
|
|
||||||
|
/// Show minimize button in window header.
|
||||||
|
pub show_minimize: bool,
|
||||||
|
|
||||||
|
/// Show maximize button in window header.
|
||||||
|
pub show_maximize: bool,
|
||||||
|
|
||||||
|
/// Preferred icon theme.
|
||||||
|
pub icon_theme: String,
|
||||||
|
|
||||||
|
/// Density of CSD/SSD header bars.
|
||||||
|
pub header_size: Density,
|
||||||
|
|
||||||
|
/// Interface density.
|
||||||
|
pub interface_density: Density,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Density {
|
||||||
|
Compact,
|
||||||
|
#[default]
|
||||||
|
Standard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CosmicTk {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
apply_theme_global: false,
|
||||||
|
show_minimize: true,
|
||||||
|
show_maximize: true,
|
||||||
|
icon_theme: String::from("Cosmic"),
|
||||||
|
header_size: Density::Standard,
|
||||||
|
interface_density: Density::Standard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CosmicTk {
|
||||||
|
pub fn config() -> Result<Config, cosmic_config::Error> {
|
||||||
|
Config::new(ID, Self::VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2024 System76 <info@system76.com>
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
//! Configurations for the libcosmic toolkit.
|
|
||||||
|
|
||||||
use cosmic_config::cosmic_config_derive::CosmicConfigEntry;
|
|
||||||
use cosmic_config::{Config, CosmicConfigEntry};
|
|
||||||
|
|
||||||
/// ID for the `CosmicTk` config.
|
|
||||||
pub const ID: &str = "com.system76.CosmicTk";
|
|
||||||
|
|
||||||
#[derive(Clone, CosmicConfigEntry, Debug, Eq, PartialEq)]
|
|
||||||
#[version = 1]
|
|
||||||
pub struct CosmicTk {
|
|
||||||
/// Show minimize button in window header.
|
|
||||||
pub show_minimize: bool,
|
|
||||||
|
|
||||||
/// Show maximize button in window header.
|
|
||||||
pub show_maximize: bool,
|
|
||||||
|
|
||||||
/// Preferred icon theme.
|
|
||||||
pub icon_theme: String,
|
|
||||||
|
|
||||||
/// Apply the theme to other toolkits.
|
|
||||||
pub apply_theme_global: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CosmicTk {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
show_minimize: true,
|
|
||||||
show_maximize: true,
|
|
||||||
icon_theme: String::from("Cosmic"),
|
|
||||||
apply_theme_global: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CosmicTk {
|
|
||||||
pub fn config() -> Result<Config, cosmic_config::Error> {
|
|
||||||
Config::new(ID, Self::VERSION)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
src/lib.rs
21
src/lib.rs
|
|
@ -29,7 +29,10 @@ pub mod command;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use cosmic_config;
|
pub use cosmic_config;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use cosmic_theme;
|
pub use cosmic_theme;
|
||||||
|
|
||||||
#[cfg(any(feature = "xdg-portal", feature = "rfd"))]
|
#[cfg(any(feature = "xdg-portal", feature = "rfd"))]
|
||||||
|
|
@ -43,15 +46,31 @@ mod ext;
|
||||||
|
|
||||||
pub mod font;
|
pub mod font;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced;
|
pub use iced;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced_core;
|
pub use iced_core;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced_futures;
|
pub use iced_futures;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced_renderer;
|
pub use iced_renderer;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced_runtime;
|
pub use iced_runtime;
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub use iced_sctk;
|
pub use iced_sctk;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced_style;
|
pub use iced_style;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced_widget;
|
pub use iced_widget;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
#[cfg(feature = "winit")]
|
#[cfg(feature = "winit")]
|
||||||
pub use iced_winit;
|
pub use iced_winit;
|
||||||
|
|
||||||
|
|
@ -67,6 +86,8 @@ pub mod process;
|
||||||
pub use cctk;
|
pub use cctk;
|
||||||
|
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use theme::{style, Theme};
|
pub use theme::{style, Theme};
|
||||||
|
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use ashpd::desktop::settings::{ColorScheme, Contrast};
|
use ashpd::desktop::settings::{ColorScheme, Contrast};
|
||||||
use ashpd::desktop::Color;
|
use ashpd::desktop::Color;
|
||||||
use iced::futures::{self, select, FutureExt, SinkExt, StreamExt};
|
use iced::futures::{self, select, FutureExt, SinkExt, StreamExt};
|
||||||
use iced::Subscription;
|
|
||||||
use iced_futures::subscription;
|
use iced_futures::subscription;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
|
@ -15,11 +14,20 @@ pub enum Desktop {
|
||||||
pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
|
pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
|
||||||
subscription::channel(std::any::TypeId::of::<Desktop>(), 10, |mut tx| {
|
subscription::channel(std::any::TypeId::of::<Desktop>(), 10, |mut tx| {
|
||||||
async move {
|
async move {
|
||||||
|
let mut attempts = 0;
|
||||||
|
loop {
|
||||||
let Ok(settings) = ashpd::desktop::settings::Settings::new().await else {
|
let Ok(settings) = ashpd::desktop::settings::Settings::new().await else {
|
||||||
// wait forever
|
|
||||||
error!("Failed to create the settings proxy");
|
error!("Failed to create the settings proxy");
|
||||||
futures::future::pending::<()>().await;
|
#[cfg(feature = "tokio")]
|
||||||
unreachable!()
|
::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts)))
|
||||||
|
.await;
|
||||||
|
#[cfg(not(feature = "tokio"))]
|
||||||
|
{
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
attempts += 1;
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
match settings.color_scheme().await {
|
match settings.color_scheme().await {
|
||||||
|
|
@ -46,6 +54,9 @@ pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
if color_scheme_stream.is_none() && contrast_stream.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
let next_color_scheme = async {
|
let next_color_scheme = async {
|
||||||
if let Some(s) = color_scheme_stream.as_mut() {
|
if let Some(s) = color_scheme_stream.as_mut() {
|
||||||
return s.next().await;
|
return s.next().await;
|
||||||
|
|
@ -77,6 +88,9 @@ pub fn desktop_settings() -> iced_futures::Subscription<Desktop> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Reset the attempts counter if we successfully received a change
|
||||||
|
attempts = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,8 @@ pub fn appearance(
|
||||||
}
|
}
|
||||||
Button::MenuRoot => {
|
Button::MenuRoot => {
|
||||||
appearance.background = None;
|
appearance.background = None;
|
||||||
appearance.icon_color = Some(cosmic.accent.base.into());
|
appearance.icon_color = None;
|
||||||
appearance.text_color = Some(cosmic.accent.base.into());
|
appearance.text_color = None;
|
||||||
}
|
}
|
||||||
Button::MenuItem => {
|
Button::MenuItem => {
|
||||||
let (background, _, _) = color(&cosmic.background.component);
|
let (background, _, _) = color(&cosmic.background.component);
|
||||||
|
|
|
||||||
|
|
@ -452,8 +452,8 @@ impl container::StyleSheet for Theme {
|
||||||
radius: [
|
radius: [
|
||||||
cosmic.corner_radii.radius_0[0],
|
cosmic.corner_radii.radius_0[0],
|
||||||
cosmic.corner_radii.radius_0[1],
|
cosmic.corner_radii.radius_0[1],
|
||||||
cosmic.corner_radii.radius_xs[2],
|
cosmic.corner_radii.radius_s[2],
|
||||||
cosmic.corner_radii.radius_xs[3],
|
cosmic.corner_radii.radius_s[3],
|
||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -494,8 +494,8 @@ impl container::StyleSheet for Theme {
|
||||||
background: Some(iced::Background::Color(cosmic.background.base.into())),
|
background: Some(iced::Background::Color(cosmic.background.base.into())),
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: [
|
radius: [
|
||||||
cosmic.corner_radii.radius_xs[0],
|
cosmic.corner_radii.radius_s[0],
|
||||||
cosmic.corner_radii.radius_xs[1],
|
cosmic.corner_radii.radius_s[1],
|
||||||
cosmic.corner_radii.radius_0[2],
|
cosmic.corner_radii.radius_0[2],
|
||||||
cosmic.corner_radii.radius_0[3],
|
cosmic.corner_radii.radius_0[3],
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct Appearance {
|
||||||
pub border_color: Color,
|
pub border_color: Color,
|
||||||
/// The expand value of the menus' background
|
/// The expand value of the menus' background
|
||||||
pub background_expand: [u16; 4],
|
pub background_expand: [u16; 4],
|
||||||
/// The highlighted path [`Color`] of the the menu bar and its menus.
|
// /// The highlighted path [`Color`] of the the menu bar and its menus.
|
||||||
pub path: Color,
|
pub path: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,27 @@ pub use self::button::Button;
|
||||||
mod dropdown;
|
mod dropdown;
|
||||||
|
|
||||||
pub mod iced;
|
pub mod iced;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::iced::Application;
|
pub use self::iced::Application;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::iced::Checkbox;
|
pub use self::iced::Checkbox;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::iced::Container;
|
pub use self::iced::Container;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::iced::ProgressBar;
|
pub use self::iced::ProgressBar;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::iced::Rule;
|
pub use self::iced::Rule;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::iced::Svg;
|
pub use self::iced::Svg;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::iced::Text;
|
pub use self::iced::Text;
|
||||||
|
|
||||||
pub mod menu_bar;
|
pub mod menu_bar;
|
||||||
|
|
||||||
mod segmented_button;
|
mod segmented_button;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::segmented_button::SegmentedButton;
|
pub use self::segmented_button::SegmentedButton;
|
||||||
|
|
||||||
mod text_input;
|
mod text_input;
|
||||||
|
#[doc(inline)]
|
||||||
pub use self::text_input::TextInput;
|
pub use self::text_input::TextInput;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! A container which constraints itself to a specific aspect ratio.
|
||||||
|
|
||||||
use iced::widget::Container;
|
use iced::widget::Container;
|
||||||
use iced::Size;
|
use iced::Size;
|
||||||
use iced_core::alignment;
|
use iced_core::alignment;
|
||||||
|
|
@ -21,9 +23,7 @@ where
|
||||||
AspectRatio::new(content, ratio)
|
AspectRatio::new(content, ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An element decorating some content.
|
/// A container which constraints itself to a specific aspect ratio.
|
||||||
///
|
|
||||||
/// It is normally used for alignment purposes.
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct AspectRatio<'a, Message, Renderer>
|
pub struct AspectRatio<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,14 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
pub type Button<'a, Message> = Builder<'a, Message, Icon>;
|
pub type Button<'a, Message> = Builder<'a, Message, Icon>;
|
||||||
|
|
||||||
|
/// The icon variant of a button.
|
||||||
pub struct Icon {
|
pub struct Icon {
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
vertical: bool,
|
vertical: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A button constructed from an icon handle, using icon button styling.
|
||||||
pub fn icon<'a, Message>(handle: impl Into<Handle>) -> Button<'a, Message> {
|
pub fn icon<'a, Message>(handle: impl Into<Handle>) -> Button<'a, Message> {
|
||||||
Button::new(Icon {
|
Button::new(Icon {
|
||||||
handle: handle.into(),
|
handle: handle.into(),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>;
|
pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>;
|
||||||
|
|
||||||
|
/// A button constructed from an image handle, using image button styling.
|
||||||
pub fn image<'a, Message>(handle: impl Into<Handle> + 'a) -> Button<'a, Message> {
|
pub fn image<'a, Message>(handle: impl Into<Handle> + 'a) -> Button<'a, Message> {
|
||||||
Button::new(Image {
|
Button::new(Image {
|
||||||
image: widget::image(handle).border_radius([9.0; 4]),
|
image: widget::image(handle).border_radius([9.0; 4]),
|
||||||
|
|
@ -19,6 +20,7 @@ pub fn image<'a, Message>(handle: impl Into<Handle> + 'a) -> Button<'a, Message>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The image variant of a button.
|
||||||
pub struct Image<'a, Handle, Message> {
|
pub struct Image<'a, Handle, Message> {
|
||||||
image: widget::Image<'a, Handle>,
|
image: widget::Image<'a, Handle>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Hyperlink button widget
|
||||||
|
|
||||||
use super::Builder;
|
use super::Builder;
|
||||||
use super::Style;
|
use super::Style;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
@ -17,6 +19,7 @@ pub struct Hyperlink {
|
||||||
trailing_icon: bool,
|
trailing_icon: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A hyperlink button.
|
||||||
pub fn link<'a, Message>(label: impl Into<Cow<'a, str>> + 'static) -> Button<'a, Message> {
|
pub fn link<'a, Message>(label: impl Into<Cow<'a, str>> + 'static) -> Button<'a, Message> {
|
||||||
Button::new(
|
Button::new(
|
||||||
label,
|
label,
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,41 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Button widgets for COSMIC applications.
|
||||||
|
|
||||||
pub use crate::theme::Button as Style;
|
pub use crate::theme::Button as Style;
|
||||||
|
|
||||||
pub mod link;
|
pub mod link;
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
|
#[doc(inline)]
|
||||||
pub use link::link;
|
pub use link::link;
|
||||||
|
#[doc(inline)]
|
||||||
pub use link::Button as LinkButton;
|
pub use link::Button as LinkButton;
|
||||||
|
|
||||||
mod icon;
|
mod icon;
|
||||||
|
#[doc(inline)]
|
||||||
pub use icon::icon;
|
pub use icon::icon;
|
||||||
|
#[doc(inline)]
|
||||||
pub use icon::Button as IconButton;
|
pub use icon::Button as IconButton;
|
||||||
|
|
||||||
mod image;
|
mod image;
|
||||||
|
#[doc(inline)]
|
||||||
pub use image::image;
|
pub use image::image;
|
||||||
|
#[doc(inline)]
|
||||||
pub use image::Button as ImageButton;
|
pub use image::Button as ImageButton;
|
||||||
|
|
||||||
mod style;
|
mod style;
|
||||||
|
#[doc(inline)]
|
||||||
pub use style::{Appearance, StyleSheet};
|
pub use style::{Appearance, StyleSheet};
|
||||||
|
|
||||||
mod text;
|
mod text;
|
||||||
|
#[doc(inline)]
|
||||||
pub use text::Button as TextButton;
|
pub use text::Button as TextButton;
|
||||||
|
#[doc(inline)]
|
||||||
pub use text::{destructive, standard, suggested, text};
|
pub use text::{destructive, standard, suggested, text};
|
||||||
|
|
||||||
mod widget;
|
mod widget;
|
||||||
|
#[doc(inline)]
|
||||||
pub use widget::{draw, focus, layout, mouse_interaction, Button};
|
pub use widget::{draw, focus, layout, mouse_interaction, Button};
|
||||||
|
|
||||||
use iced_core::font::Weight;
|
use iced_core::font::Weight;
|
||||||
|
|
@ -31,10 +43,12 @@ use iced_core::widget::Id;
|
||||||
use iced_core::{Length, Padding};
|
use iced_core::{Length, Padding};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// A button with the default style, which may contain any widget as its content.
|
||||||
pub fn button<'a, Message>(content: impl Into<crate::Element<'a, Message>>) -> Button<'a, Message> {
|
pub fn button<'a, Message>(content: impl Into<crate::Element<'a, Message>>) -> Button<'a, Message> {
|
||||||
Button::new(content)
|
Button::new(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An image button which may contain any widget as its content.
|
||||||
pub fn custom_image_button<'a, Message>(
|
pub fn custom_image_button<'a, Message>(
|
||||||
content: impl Into<crate::Element<'a, Message>>,
|
content: impl Into<crate::Element<'a, Message>>,
|
||||||
on_remove: Option<Message>,
|
on_remove: Option<Message>,
|
||||||
|
|
@ -42,6 +56,7 @@ pub fn custom_image_button<'a, Message>(
|
||||||
Button::new_image(content, on_remove)
|
Button::new_image(content, on_remove)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A builder for constructing a custom [`Button`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[derive(Setters)]
|
#[derive(Setters)]
|
||||||
pub struct Builder<'a, Message, Variant> {
|
pub struct Builder<'a, Message, Variant> {
|
||||||
|
|
|
||||||
|
|
@ -10,26 +10,31 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
pub type Button<'a, Message> = Builder<'a, Message, Text>;
|
pub type Button<'a, Message> = Builder<'a, Message, Text>;
|
||||||
|
|
||||||
|
/// A text button with the destructive style
|
||||||
pub fn destructive<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
pub fn destructive<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
||||||
Button::new(Text::new())
|
Button::new(Text::new())
|
||||||
.label(label)
|
.label(label)
|
||||||
.style(Style::Destructive)
|
.style(Style::Destructive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A text button with the suggested style
|
||||||
pub fn suggested<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
pub fn suggested<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
||||||
Button::new(Text::new())
|
Button::new(Text::new())
|
||||||
.label(label)
|
.label(label)
|
||||||
.style(Style::Suggested)
|
.style(Style::Suggested)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A text button with the standard style
|
||||||
pub fn standard<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
pub fn standard<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
||||||
Button::new(Text::new()).label(label)
|
Button::new(Text::new()).label(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A text button with the text style
|
||||||
pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
pub fn text<'a, Message>(label: impl Into<Cow<'a, str>>) -> Button<'a, Message> {
|
||||||
Button::new(Text::new()).label(label).style(Style::Text)
|
Button::new(Text::new()).label(label).style(Style::Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The text variant of a button.
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
pub(super) leading_icon: Option<icon::Handle>,
|
pub(super) leading_icon: Option<icon::Handle>,
|
||||||
pub(super) trailing_icon: Option<icon::Handle>,
|
pub(super) trailing_icon: Option<icon::Handle>,
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ pub struct Button<'a, Message> {
|
||||||
|
|
||||||
impl<'a, Message> Button<'a, Message> {
|
impl<'a, Message> Button<'a, Message> {
|
||||||
/// Creates a new [`Button`] with the given content.
|
/// Creates a new [`Button`] with the given content.
|
||||||
pub fn new(content: impl Into<crate::Element<'a, Message>>) -> Self {
|
pub(super) fn new(content: impl Into<crate::Element<'a, Message>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Id::unique(),
|
id: Id::unique(),
|
||||||
#[cfg(feature = "a11y")]
|
#[cfg(feature = "a11y")]
|
||||||
|
|
@ -375,7 +375,13 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color);
|
let mut icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color);
|
||||||
let mut text_color = styling.text_color.unwrap_or(renderer_style.text_color);
|
|
||||||
|
// Menu roots should share the accent color that icons get in the header.
|
||||||
|
let mut text_color = if matches!(self.style, crate::theme::Button::MenuRoot) {
|
||||||
|
icon_color
|
||||||
|
} else {
|
||||||
|
styling.text_color.unwrap_or(renderer_style.text_color)
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(alpha) = headerbar_alpha {
|
if let Some(alpha) = headerbar_alpha {
|
||||||
icon_color.a = alpha;
|
icon_color.a = alpha;
|
||||||
|
|
@ -403,10 +409,37 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
on_remove,
|
on_remove,
|
||||||
} = &self.variant
|
} = &self.variant
|
||||||
{
|
{
|
||||||
|
let mut parent_bounds = bounds;
|
||||||
|
parent_bounds.y -= 8.0;
|
||||||
|
parent_bounds.width += 16.0;
|
||||||
|
parent_bounds.height += 16.0;
|
||||||
|
|
||||||
|
renderer.with_layer(parent_bounds, |renderer| {
|
||||||
let selection_background = theme.selection_background();
|
let selection_background = theme.selection_background();
|
||||||
|
|
||||||
let c_rad = THEME.with(|t| t.borrow().cosmic().corner_radii);
|
let c_rad = THEME.with(|t| t.borrow().cosmic().corner_radii);
|
||||||
|
|
||||||
|
// NOTE: Workaround to round the border of the unselected, unhovered image.
|
||||||
|
if !self.selected && !is_mouse_over {
|
||||||
|
let mut bounds = bounds;
|
||||||
|
bounds.x -= 2.0;
|
||||||
|
bounds.y -= 2.0;
|
||||||
|
bounds.width += 4.0;
|
||||||
|
bounds.height += 4.0;
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border: Border {
|
||||||
|
width: 2.0,
|
||||||
|
color: crate::theme::active().current_container().base.into(),
|
||||||
|
radius: 9.0.into(),
|
||||||
|
},
|
||||||
|
shadow: Shadow::default(),
|
||||||
|
},
|
||||||
|
Color::TRANSPARENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if self.selected {
|
if self.selected {
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
Quad {
|
Quad {
|
||||||
|
|
@ -474,6 +507,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -519,7 +553,7 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
let child_tree = self
|
let child_tree = self
|
||||||
.content
|
.content
|
||||||
.as_widget()
|
.as_widget()
|
||||||
.a11y_nodes(child_layout, &child_tree, p);
|
.a11y_nodes(child_layout, child_tree, p);
|
||||||
|
|
||||||
let Rectangle {
|
let Rectangle {
|
||||||
x,
|
x,
|
||||||
|
|
@ -775,6 +809,24 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
||||||
// Then draw the button contents onto the background.
|
// Then draw the button contents onto the background.
|
||||||
draw_contents(renderer, styling);
|
draw_contents(renderer, styling);
|
||||||
|
|
||||||
|
let mut clipped_bounds = bounds;
|
||||||
|
clipped_bounds.height += styling.border_width;
|
||||||
|
|
||||||
|
renderer.with_layer(clipped_bounds, |renderer| {
|
||||||
|
// NOTE: Workaround to round the border of the hovered/selected image.
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border: Border {
|
||||||
|
width: styling.border_width,
|
||||||
|
color: crate::theme::active().current_container().base.into(),
|
||||||
|
radius: 0.0.into(),
|
||||||
|
},
|
||||||
|
shadow: Shadow::default(),
|
||||||
|
},
|
||||||
|
Color::TRANSPARENT,
|
||||||
|
);
|
||||||
|
|
||||||
// Finish by drawing the border above the contents.
|
// Finish by drawing the border above the contents.
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
|
|
@ -788,6 +840,7 @@ pub fn draw<Renderer: iced_core::Renderer, Theme>(
|
||||||
},
|
},
|
||||||
Color::TRANSPARENT,
|
Color::TRANSPARENT,
|
||||||
);
|
);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
draw_contents(renderer, styling);
|
draw_contents(renderer, styling);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2024 System76 <info@system76.com>
|
// Copyright 2024 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! A widget that displays an interactive calendar.
|
||||||
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
|
||||||
use crate::iced_core::{Length, Padding};
|
use crate::iced_core::{Length, Padding};
|
||||||
|
|
@ -8,6 +10,7 @@ use crate::widget::{button, column, grid, icon, row, text, Grid};
|
||||||
use chrono::{Datelike, Days, Months, NaiveDate, Weekday};
|
use chrono::{Datelike, Days, Months, NaiveDate, Weekday};
|
||||||
use iced::alignment::{Horizontal, Vertical};
|
use iced::alignment::{Horizontal, Vertical};
|
||||||
|
|
||||||
|
/// A widget that displays an interactive calendar.
|
||||||
pub fn calendar<M>(
|
pub fn calendar<M>(
|
||||||
selected: &NaiveDate,
|
selected: &NaiveDate,
|
||||||
on_select: impl Fn(NaiveDate) -> M + 'static,
|
on_select: impl Fn(NaiveDate) -> M + 'static,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Widgets for selecting colors with a color picker.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
@ -29,9 +31,12 @@ use palette::{FromColor, RgbHue};
|
||||||
use super::button::StyleSheet;
|
use super::button::StyleSheet;
|
||||||
use super::divider::horizontal;
|
use super::divider::horizontal;
|
||||||
use super::icon::{self, from_name};
|
use super::icon::{self, from_name};
|
||||||
use super::segmented_button::{self, Model, SingleSelect};
|
use super::segmented_button::{self, SingleSelect};
|
||||||
use super::{button, segmented_control, text, text_input, tooltip, Icon};
|
use super::{button, segmented_control, text, text_input, tooltip, Icon};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use ColorPickerModel as Model;
|
||||||
|
|
||||||
// TODO is this going to look correct enough?
|
// TODO is this going to look correct enough?
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref HSV_RAINBOW: Vec<ColorStop> = (0u16..8)
|
pub static ref HSV_RAINBOW: Vec<ColorStop> = (0u16..8)
|
||||||
|
|
@ -64,7 +69,7 @@ pub enum ColorPickerUpdate {
|
||||||
#[derive(Setters)]
|
#[derive(Setters)]
|
||||||
pub struct ColorPickerModel {
|
pub struct ColorPickerModel {
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
segmented_model: Model<SingleSelect>,
|
segmented_model: segmented_button::Model<SingleSelect>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
active_color: palette::Hsv,
|
active_color: palette::Hsv,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
|
|
@ -246,7 +251,7 @@ impl ColorPickerModel {
|
||||||
#[derive(Setters, Clone)]
|
#[derive(Setters, Clone)]
|
||||||
pub struct ColorPickerBuilder<'a, Message> {
|
pub struct ColorPickerBuilder<'a, Message> {
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
model: &'a Model<SingleSelect>,
|
model: &'a segmented_button::Model<SingleSelect>,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
active_color: palette::Hsv,
|
active_color: palette::Hsv,
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
|
|
@ -757,6 +762,7 @@ fn color_to_string(c: palette::Hsv, is_hex: bool) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A button for selecting a color from a color picker.
|
||||||
pub fn color_button<'a, Message: 'static>(
|
pub fn color_button<'a, Message: 'static>(
|
||||||
on_press: Option<Message>,
|
on_press: Option<Message>,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! An overlayed widget that attaches a toggleable context drawer to the view.
|
||||||
|
|
||||||
mod overlay;
|
mod overlay;
|
||||||
|
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
@ -8,6 +10,7 @@ pub use widget::ContextDrawer;
|
||||||
|
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
|
||||||
|
/// An overlayed widget that attaches a toggleable context drawer to the view.
|
||||||
pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>(
|
pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>(
|
||||||
header: &'a str,
|
header: &'a str,
|
||||||
on_close: Message,
|
on_close: Message,
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
self.content.as_widget_mut().diff(&mut tree.children[0]);
|
tree.children[0].diff(self.content.as_widget_mut());
|
||||||
|
|
||||||
// if let Some(ref mut context_menus) = self.context_menu {
|
// if let Some(ref mut context_menus) = self.context_menu {
|
||||||
// for (menu, tree) in context_menus
|
// for (menu, tree) in context_menus
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ impl<'a, Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
self.container.as_widget_mut().diff(&mut tree.children[0]);
|
tree.children[0].diff(self.container.as_widget_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> iced_core::widget::tree::State {
|
fn state(&self) -> iced_core::widget::tree::State {
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ impl<
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
self.container.as_widget_mut().diff(&mut tree.children[0]);
|
tree.children[0].diff(self.container.as_widget_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> iced_core::widget::tree::State {
|
fn state(&self) -> iced_core::widget::tree::State {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
// Copyright 2019 Héctor Ramón, Iced contributors
|
// Copyright 2019 Héctor Ramón, Iced contributors
|
||||||
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
||||||
|
|
||||||
|
//! Displays a list of options in a popover menu on select.
|
||||||
|
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
|
|
||||||
|
|
@ -10,6 +12,7 @@ pub mod multi;
|
||||||
mod widget;
|
mod widget;
|
||||||
pub use widget::*;
|
pub use widget::*;
|
||||||
|
|
||||||
|
/// Displays a list of options in a popover menu on select.
|
||||||
pub fn dropdown<'a, S: AsRef<str>, Message: 'a>(
|
pub fn dropdown<'a, S: AsRef<str>, Message: 'a>(
|
||||||
selections: &'a [S],
|
selections: &'a [S],
|
||||||
selected: Option<usize>,
|
selected: Option<usize>,
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,14 @@
|
||||||
use crate::{Element, Renderer};
|
use crate::{Element, Renderer};
|
||||||
use iced_core::layout::{Limits, Node};
|
use iced_core::layout::{Limits, Node};
|
||||||
use iced_core::widget::Tree;
|
use iced_core::widget::Tree;
|
||||||
use iced_core::{Padding, Point, Size};
|
use iced_core::{Length, Padding, Point, Size};
|
||||||
|
use taffy::geometry::Rect;
|
||||||
|
use taffy::style::{AlignItems, Dimension, Display, Style};
|
||||||
|
use taffy::style_helpers::length;
|
||||||
|
use taffy::{AlignContent, TaffyTree};
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn resolve<Message>(
|
pub fn resolve<Message>(
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &Limits,
|
limits: &Limits,
|
||||||
|
|
@ -13,79 +19,153 @@ pub fn resolve<Message>(
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
column_spacing: f32,
|
column_spacing: f32,
|
||||||
row_spacing: f32,
|
row_spacing: f32,
|
||||||
|
min_item_width: Option<f32>,
|
||||||
|
justify_items: Option<AlignItems>,
|
||||||
|
align_items: Option<AlignItems>,
|
||||||
|
justify_content: Option<AlignContent>,
|
||||||
tree: &mut [Tree],
|
tree: &mut [Tree],
|
||||||
) -> Node {
|
) -> Node {
|
||||||
let limits = limits.shrink(padding);
|
let max_size = limits.max();
|
||||||
|
|
||||||
|
let mut leafs = Vec::with_capacity(items.len());
|
||||||
let mut nodes = Vec::with_capacity(items.len());
|
let mut nodes = Vec::with_capacity(items.len());
|
||||||
|
|
||||||
let max_flex_width = limits.max().width;
|
let mut taffy_tree = TaffyTree::<()>::with_capacity(items.len() + 1);
|
||||||
let mut flex_width = 0.0f32;
|
|
||||||
let mut flex_height = 0.0f32;
|
|
||||||
|
|
||||||
let mut current_row_width = 0.0f32;
|
let style = taffy::Style {
|
||||||
let mut current_row_height = 0.0f32;
|
display: Display::Flex,
|
||||||
|
flex_direction: taffy::FlexDirection::Row,
|
||||||
|
flex_wrap: taffy::FlexWrap::Wrap,
|
||||||
|
|
||||||
let mut row_buffer = Vec::<Node>::with_capacity(8);
|
gap: taffy::geometry::Size {
|
||||||
|
width: length(column_spacing),
|
||||||
|
height: length(row_spacing),
|
||||||
|
},
|
||||||
|
|
||||||
for (child, tree) in items.iter().zip(tree.iter_mut()) {
|
min_size: taffy::geometry::Size {
|
||||||
// Calculate the dimensions of the item.
|
width: length(max_size.width),
|
||||||
let child_node = child.as_widget().layout(tree, renderer, &limits);
|
height: Dimension::Auto,
|
||||||
let size = child_node.size();
|
},
|
||||||
|
|
||||||
// Calculate the required additional width to fit the item into the current row.
|
align_items,
|
||||||
let mut required_width = size.width
|
justify_items,
|
||||||
+ if row_buffer.is_empty() {
|
justify_content,
|
||||||
0.0
|
|
||||||
} else {
|
padding: Rect {
|
||||||
row_spacing
|
left: length(padding.left),
|
||||||
|
right: length(padding.right),
|
||||||
|
top: length(padding.top),
|
||||||
|
bottom: length(padding.bottom),
|
||||||
|
},
|
||||||
|
|
||||||
|
..taffy::Style::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// If it fits, add it to the current row, or create a new one.
|
for (child, tree) in items.iter().zip(tree.iter_mut()) {
|
||||||
if current_row_width + required_width > max_flex_width {
|
let child_widget = child.as_widget();
|
||||||
if flex_height != 0.0f32 {
|
let child_node = child_widget.layout(tree, renderer, limits);
|
||||||
flex_height += column_spacing;
|
let size = child_node.size();
|
||||||
}
|
|
||||||
|
|
||||||
let mut pos_x = 0.0f32;
|
|
||||||
let pos_y = flex_height;
|
|
||||||
|
|
||||||
for mut child_node in row_buffer.drain(..) {
|
|
||||||
child_node = child_node.move_to(Point::new(pos_x, pos_y));
|
|
||||||
pos_x += row_spacing + child_node.size().width;
|
|
||||||
nodes.push(child_node);
|
nodes.push(child_node);
|
||||||
|
|
||||||
|
let c_size = child_widget.size();
|
||||||
|
let (width, flex_grow, justify_self) = match c_size.width {
|
||||||
|
Length::Fill | Length::FillPortion(_) => {
|
||||||
|
(Dimension::Auto, 1.0, Some(AlignItems::Stretch))
|
||||||
|
}
|
||||||
|
_ => (length(size.width), 0.0, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let child_style = Style {
|
||||||
|
flex_grow,
|
||||||
|
|
||||||
|
min_size: taffy::geometry::Size {
|
||||||
|
width: match min_item_width {
|
||||||
|
Some(width) => length(size.width.min(width)),
|
||||||
|
None => Dimension::Auto,
|
||||||
|
},
|
||||||
|
height: Dimension::Auto,
|
||||||
|
},
|
||||||
|
|
||||||
|
size: taffy::geometry::Size {
|
||||||
|
width,
|
||||||
|
height: match c_size.height {
|
||||||
|
Length::Fill | Length::FillPortion(_) => Dimension::Auto,
|
||||||
|
_ => length(size.height),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
justify_self,
|
||||||
|
|
||||||
|
..Style::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
leafs.push(match taffy_tree.new_leaf(child_style) {
|
||||||
|
Ok(leaf) => leaf,
|
||||||
|
Err(why) => {
|
||||||
|
tracing::error!(?why, "failed to add child element to flex row");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
flex_height += current_row_height;
|
let root = match taffy_tree.new_with_children(style, &leafs) {
|
||||||
flex_width = flex_width.max(current_row_width);
|
Ok(root) => root,
|
||||||
required_width -= row_spacing;
|
Err(why) => {
|
||||||
current_row_width = 0.0;
|
tracing::error!(?why, "flex row style is invalid");
|
||||||
|
return Node::new(Size::ZERO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(why) = taffy_tree.compute_layout(
|
||||||
|
root,
|
||||||
|
taffy::geometry::Size {
|
||||||
|
width: length(max_size.width),
|
||||||
|
height: length(max_size.height),
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
tracing::error!(?why, "flex row layout invalid");
|
||||||
|
return Node::new(Size::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
current_row_width += required_width;
|
let flex_layout = match taffy_tree.layout(root) {
|
||||||
current_row_height = current_row_height.max(size.height);
|
Ok(layout) => layout,
|
||||||
|
Err(why) => {
|
||||||
|
tracing::error!(?why, "cannot get flex row layout");
|
||||||
|
return Node::new(Size::ZERO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
row_buffer.push(child_node);
|
leafs
|
||||||
|
.into_iter()
|
||||||
|
.zip(items.iter())
|
||||||
|
.zip(nodes.iter_mut())
|
||||||
|
.zip(tree)
|
||||||
|
.for_each(|(((leaf, child), node), tree)| {
|
||||||
|
let Ok(leaf_layout) = taffy_tree.layout(leaf) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let child_widget = child.as_widget();
|
||||||
|
let c_size = child_widget.size();
|
||||||
|
match c_size.width {
|
||||||
|
Length::Fill | Length::FillPortion(_) => {
|
||||||
|
*node =
|
||||||
|
child_widget.layout(tree, renderer, &limits.width(leaf_layout.size.width));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !row_buffer.is_empty() {
|
*node = node.clone().move_to(Point {
|
||||||
if flex_height != 0.0f32 {
|
x: leaf_layout.location.x,
|
||||||
flex_height += column_spacing;
|
y: leaf_layout.location.y,
|
||||||
}
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let mut pos_x = 0.0f32;
|
let size = Size {
|
||||||
let pos_y = flex_height;
|
width: flex_layout.content_size.width,
|
||||||
|
height: flex_layout.content_size.height,
|
||||||
|
};
|
||||||
|
|
||||||
for mut child_node in row_buffer.drain(..) {
|
Node::with_children(size, nodes)
|
||||||
child_node = child_node.move_to(Point::new(pos_x, pos_y));
|
|
||||||
pos_x += row_spacing + child_node.size().width;
|
|
||||||
nodes.push(child_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
flex_height += current_row_height;
|
|
||||||
flex_width = flex_width.max(current_row_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
let flex_size = limits.resolve(flex_width, flex_height, Size::new(flex_width, flex_height));
|
|
||||||
Node::with_children(flex_size.expand(padding), nodes)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Responsively generates rows of widgets based on the dimensions of its children.
|
||||||
|
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
|
||||||
|
|
@ -8,7 +10,7 @@ pub use widget::FlexRow;
|
||||||
|
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
|
||||||
/// Responsively generates rows and columns of widgets based on its dimmensions.
|
/// Responsively generates rows of widgets based on the dimensions of its children.
|
||||||
pub const fn flex_row<Message>(children: Vec<Element<Message>>) -> FlexRow<Message> {
|
pub const fn flex_row<Message>(children: Vec<Element<Message>>) -> FlexRow<Message> {
|
||||||
FlexRow::new(children)
|
FlexRow::new(children)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ pub struct FlexRow<'a, Message> {
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
children: Vec<Element<'a, Message>>,
|
children: Vec<Element<'a, Message>>,
|
||||||
/// Sets the padding around the widget.
|
/// Sets the padding around the widget.
|
||||||
|
#[setters(into)]
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
/// Sets the space between each column of items.
|
/// Sets the space between each column of items.
|
||||||
column_spacing: u16,
|
column_spacing: u16,
|
||||||
|
|
@ -24,21 +25,64 @@ pub struct FlexRow<'a, Message> {
|
||||||
row_spacing: u16,
|
row_spacing: u16,
|
||||||
/// Sets the width.
|
/// Sets the width.
|
||||||
width: Length,
|
width: Length,
|
||||||
|
/// Sets minimum width of items that grow.
|
||||||
|
#[setters(into)]
|
||||||
|
min_item_width: Option<f32>,
|
||||||
/// Sets the max width
|
/// Sets the max width
|
||||||
max_width: f32,
|
max_width: f32,
|
||||||
|
/// Defines how content will be aligned horizontally.
|
||||||
|
#[setters(skip)]
|
||||||
|
align_items: Option<taffy::AlignItems>,
|
||||||
|
/// Defines how content will be aligned vertically.
|
||||||
|
#[setters(skip)]
|
||||||
|
justify_items: Option<taffy::AlignItems>,
|
||||||
|
/// Defines how the content will be justified.
|
||||||
|
#[setters(into)]
|
||||||
|
justify_content: Option<crate::widget::JustifyContent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message> FlexRow<'a, Message> {
|
impl<'a, Message> FlexRow<'a, Message> {
|
||||||
pub const fn new(children: Vec<Element<'a, Message>>) -> Self {
|
pub(crate) const fn new(children: Vec<Element<'a, Message>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
children,
|
children,
|
||||||
padding: Padding::ZERO,
|
padding: Padding::ZERO,
|
||||||
column_spacing: 4,
|
column_spacing: 4,
|
||||||
row_spacing: 4,
|
row_spacing: 4,
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
|
min_item_width: None,
|
||||||
max_width: f32::INFINITY,
|
max_width: f32::INFINITY,
|
||||||
|
align_items: None,
|
||||||
|
justify_items: None,
|
||||||
|
justify_content: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines how content will be aligned horizontally.
|
||||||
|
pub fn align_items(mut self, alignment: iced::Alignment) -> Self {
|
||||||
|
self.align_items = Some(match alignment {
|
||||||
|
iced::Alignment::Center => taffy::AlignItems::Center,
|
||||||
|
iced::Alignment::Start => taffy::AlignItems::Start,
|
||||||
|
iced::Alignment::End => taffy::AlignItems::End,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines how content will be aligned vertically.
|
||||||
|
pub fn justify_items(mut self, alignment: iced::Alignment) -> Self {
|
||||||
|
self.justify_items = Some(match alignment {
|
||||||
|
iced::Alignment::Center => taffy::AlignItems::Center,
|
||||||
|
iced::Alignment::Start => taffy::AlignItems::Start,
|
||||||
|
iced::Alignment::End => taffy::AlignItems::End,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the space between each column and row.
|
||||||
|
pub const fn spacing(mut self, spacing: u16) -> Self {
|
||||||
|
self.column_spacing = spacing;
|
||||||
|
self.row_spacing = spacing;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
|
impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
|
||||||
|
|
@ -75,6 +119,10 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer>
|
||||||
self.padding,
|
self.padding,
|
||||||
f32::from(self.column_spacing),
|
f32::from(self.column_spacing),
|
||||||
f32::from(self.row_spacing),
|
f32::from(self.row_spacing),
|
||||||
|
self.min_item_width,
|
||||||
|
self.align_items,
|
||||||
|
self.justify_items,
|
||||||
|
self.justify_content,
|
||||||
&mut tree.children,
|
&mut tree.children,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ use iced_core::{Alignment, Length, Padding, Point, Size};
|
||||||
use taffy::geometry::{Line, Rect};
|
use taffy::geometry::{Line, Rect};
|
||||||
use taffy::style::{AlignItems, Dimension, Display, GridPlacement, Style};
|
use taffy::style::{AlignItems, Dimension, Display, GridPlacement, Style};
|
||||||
use taffy::style_helpers::{auto, length};
|
use taffy::style_helpers::{auto, length};
|
||||||
use taffy::TaffyTree;
|
use taffy::{AlignContent, TaffyTree};
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn resolve<Message>(
|
pub fn resolve<Message>(
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
|
|
@ -23,6 +24,7 @@ pub fn resolve<Message>(
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
column_alignment: Alignment,
|
column_alignment: Alignment,
|
||||||
row_alignment: Alignment,
|
row_alignment: Alignment,
|
||||||
|
justify_content: Option<AlignContent>,
|
||||||
column_spacing: f32,
|
column_spacing: f32,
|
||||||
row_spacing: f32,
|
row_spacing: f32,
|
||||||
tree: &mut [Tree],
|
tree: &mut [Tree],
|
||||||
|
|
@ -44,23 +46,29 @@ pub fn resolve<Message>(
|
||||||
nodes.push(child_node);
|
nodes.push(child_node);
|
||||||
|
|
||||||
let c_size = child_widget.size();
|
let c_size = child_widget.size();
|
||||||
let (width, justify_self) = match c_size.width {
|
let (width, flex_grow, justify_self) = match c_size.width {
|
||||||
Length::Fill | Length::FillPortion(_) => (Dimension::Auto, Some(AlignItems::Stretch)),
|
Length::Fill | Length::FillPortion(_) => {
|
||||||
_ => (length(size.width), None),
|
(Dimension::Auto, 1.0, Some(AlignItems::Stretch))
|
||||||
|
}
|
||||||
|
_ => (length(size.width), 0.0, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attach widget as leaf to be later assigned to grid.
|
// Attach widget as leaf to be later assigned to grid.
|
||||||
let leaf = taffy.new_leaf(Style {
|
let leaf = taffy.new_leaf(Style {
|
||||||
|
flex_grow,
|
||||||
|
|
||||||
grid_column: Line {
|
grid_column: Line {
|
||||||
start: GridPlacement::Line((assignment.column as i16).into()),
|
start: GridPlacement::Line((assignment.column as i16).into()),
|
||||||
end: GridPlacement::Line(
|
end: GridPlacement::Line(
|
||||||
(assignment.column as i16 + assignment.width as i16).into(),
|
(assignment.column as i16 + assignment.width as i16).into(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
grid_row: Line {
|
grid_row: Line {
|
||||||
start: GridPlacement::Line((assignment.row as i16).into()),
|
start: GridPlacement::Line((assignment.row as i16).into()),
|
||||||
end: GridPlacement::Line((assignment.row as i16 + assignment.height as i16).into()),
|
end: GridPlacement::Line((assignment.row as i16 + assignment.height as i16).into()),
|
||||||
},
|
},
|
||||||
|
|
||||||
size: taffy::geometry::Size {
|
size: taffy::geometry::Size {
|
||||||
width,
|
width,
|
||||||
height: match c_size.height {
|
height: match c_size.height {
|
||||||
|
|
@ -68,14 +76,16 @@ pub fn resolve<Message>(
|
||||||
_ => length(size.height),
|
_ => length(size.height),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
justify_self,
|
justify_self,
|
||||||
|
|
||||||
..Style::default()
|
..Style::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
match leaf {
|
match leaf {
|
||||||
Ok(leaf) => leafs.push(leaf),
|
Ok(leaf) => leafs.push(leaf),
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
tracing::error!(%why, "cannot add leaf node to grid");
|
tracing::error!(?why, "cannot add leaf node to grid");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,6 +118,8 @@ pub fn resolve<Message>(
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
justify_content,
|
||||||
|
|
||||||
padding: Rect {
|
padding: Rect {
|
||||||
left: length(padding.left),
|
left: length(padding.left),
|
||||||
right: length(padding.right),
|
right: length(padding.right),
|
||||||
|
|
@ -134,7 +146,7 @@ pub fn resolve<Message>(
|
||||||
let root = match root {
|
let root = match root {
|
||||||
Ok(root) => root,
|
Ok(root) => root,
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
tracing::error!(%why, "grid root style invalid");
|
tracing::error!(?why, "grid root style invalid");
|
||||||
return Node::new(Size::ZERO);
|
return Node::new(Size::ZERO);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -146,14 +158,14 @@ pub fn resolve<Message>(
|
||||||
height: length(max_size.height),
|
height: length(max_size.height),
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
tracing::error!(%why, "grid layout did not compute");
|
tracing::error!(?why, "grid layout did not compute");
|
||||||
return Node::new(Size::ZERO);
|
return Node::new(Size::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
let grid_layout = match taffy.layout(root) {
|
let grid_layout = match taffy.layout(root) {
|
||||||
Ok(layout) => layout,
|
Ok(layout) => layout,
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
tracing::error!(%why, "cannot get layout of grid");
|
tracing::error!(?why, "cannot get layout of grid");
|
||||||
return Node::new(Size::ZERO);
|
return Node::new(Size::ZERO);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -183,9 +195,9 @@ pub fn resolve<Message>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let grid_size = Size {
|
let grid_size = Size {
|
||||||
width: grid_layout.size.width,
|
width: grid_layout.content_size.width,
|
||||||
height: grid_layout.size.height,
|
height: grid_layout.content_size.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
Node::with_children(grid_size.expand(padding), nodes)
|
Node::with_children(grid_size, nodes)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Arrange widgets with a grid layout.
|
||||||
|
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
|
||||||
pub use widget::Grid;
|
pub use widget::Grid;
|
||||||
|
|
||||||
/// Responsively generates rows and columns of widgets based on its dimmensions.
|
/// Arrange widgets with a grid layout.
|
||||||
pub const fn grid<'a, Message>() -> Grid<'a, Message> {
|
pub const fn grid<'a, Message>() -> Grid<'a, Message> {
|
||||||
Grid::new()
|
Grid::new()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ pub struct Grid<'a, Message> {
|
||||||
column_alignment: Alignment,
|
column_alignment: Alignment,
|
||||||
/// Alignment across rows
|
/// Alignment across rows
|
||||||
row_alignment: Alignment,
|
row_alignment: Alignment,
|
||||||
|
/// Defines how the content will be justified.
|
||||||
|
#[setters(into, strip_option)]
|
||||||
|
justify_content: Option<crate::widget::JustifyContent>,
|
||||||
/// Sets the space between each column of items.
|
/// Sets the space between each column of items.
|
||||||
column_spacing: u16,
|
column_spacing: u16,
|
||||||
/// Sets the space between each item in a row.
|
/// Sets the space between each item in a row.
|
||||||
|
|
@ -50,6 +53,7 @@ impl<'a, Message> Grid<'a, Message> {
|
||||||
padding: Padding::ZERO,
|
padding: Padding::ZERO,
|
||||||
column_alignment: Alignment::Start,
|
column_alignment: Alignment::Start,
|
||||||
row_alignment: Alignment::Start,
|
row_alignment: Alignment::Start,
|
||||||
|
justify_content: None,
|
||||||
column_spacing: 4,
|
column_spacing: 4,
|
||||||
row_spacing: 4,
|
row_spacing: 4,
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
|
|
@ -138,6 +142,7 @@ impl<'a, Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for G
|
||||||
self.padding,
|
self.padding,
|
||||||
self.column_alignment,
|
self.column_alignment,
|
||||||
self.row_alignment,
|
self.row_alignment,
|
||||||
|
self.justify_content,
|
||||||
f32::from(self.column_spacing),
|
f32::from(self.column_spacing),
|
||||||
f32::from(self.row_spacing),
|
f32::from(self.row_spacing),
|
||||||
&mut tree.children,
|
&mut tree.children,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::config::Density;
|
||||||
use crate::{ext::CollectionWidget, widget, Element};
|
use crate::{ext::CollectionWidget, widget, Element};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::Length;
|
use iced::{Length, Padding};
|
||||||
use iced_core::{widget::tree, Widget};
|
use iced_core::{widget::tree, Widget};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
||||||
start: Vec::new(),
|
start: Vec::new(),
|
||||||
center: Vec::new(),
|
center: Vec::new(),
|
||||||
end: Vec::new(),
|
end: Vec::new(),
|
||||||
|
density: None,
|
||||||
focused: false,
|
focused: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,9 +52,6 @@ pub struct HeaderBar<'a, Message> {
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
on_right_click: Option<Message>,
|
on_right_click: Option<Message>,
|
||||||
|
|
||||||
/// Focused state of the window
|
|
||||||
focused: bool,
|
|
||||||
|
|
||||||
/// Elements packed at the start of the headerbar.
|
/// Elements packed at the start of the headerbar.
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
start: Vec<Element<'a, Message>>,
|
start: Vec<Element<'a, Message>>,
|
||||||
|
|
@ -64,6 +63,13 @@ pub struct HeaderBar<'a, Message> {
|
||||||
/// Elements packed at the end of the headerbar.
|
/// Elements packed at the end of the headerbar.
|
||||||
#[setters(skip)]
|
#[setters(skip)]
|
||||||
end: Vec<Element<'a, Message>>,
|
end: Vec<Element<'a, Message>>,
|
||||||
|
|
||||||
|
/// Controls the density of the headerbar.
|
||||||
|
#[setters(strip_option)]
|
||||||
|
density: Option<Density>,
|
||||||
|
|
||||||
|
/// Focused state of the window
|
||||||
|
focused: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
|
|
@ -99,7 +105,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn build(self) -> HeaderBarWidget<'a, Message> {
|
pub fn build(self) -> HeaderBarWidget<'a, Message> {
|
||||||
HeaderBarWidget {
|
HeaderBarWidget {
|
||||||
header_bar_inner: self.into_element(),
|
header_bar_inner: self.view(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +259,7 @@ impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
/// Converts the headerbar builder into an Iced element.
|
/// Converts the headerbar builder into an Iced element.
|
||||||
pub fn into_element(mut self) -> Element<'a, Message> {
|
pub fn view(mut self) -> Element<'a, Message> {
|
||||||
// Take ownership of the regions to be packed.
|
// Take ownership of the regions to be packed.
|
||||||
let start = std::mem::take(&mut self.start);
|
let start = std::mem::take(&mut self.start);
|
||||||
let center = std::mem::take(&mut self.center);
|
let center = std::mem::take(&mut self.center);
|
||||||
|
|
@ -263,6 +269,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
end.push(widget::horizontal_space(Length::Fixed(12.0)).into());
|
end.push(widget::horizontal_space(Length::Fixed(12.0)).into());
|
||||||
end.push(self.window_controls());
|
end.push(self.window_controls());
|
||||||
|
|
||||||
|
let (height, padding) = match self.density.unwrap_or_else(crate::config::header_size) {
|
||||||
|
crate::config::Density::Compact => (36.0, 2.0),
|
||||||
|
crate::config::Density::Standard => (48.0, 8.0),
|
||||||
|
};
|
||||||
|
|
||||||
// Creates the headerbar widget.
|
// Creates the headerbar widget.
|
||||||
let mut widget = widget::row::with_capacity(4)
|
let mut widget = widget::row::with_capacity(4)
|
||||||
// If elements exist in the start region, append them here.
|
// If elements exist in the start region, append them here.
|
||||||
|
|
@ -295,9 +306,9 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
.width(Length::Shrink),
|
.width(Length::Shrink),
|
||||||
)
|
)
|
||||||
.align_items(iced::Alignment::Center)
|
.align_items(iced::Alignment::Center)
|
||||||
.height(Length::Fixed(50.0))
|
.height(Length::Fixed(height))
|
||||||
.padding(8)
|
.padding(padding)
|
||||||
.spacing(8)
|
.spacing(padding)
|
||||||
.apply(widget::container)
|
.apply(widget::container)
|
||||||
.style(crate::theme::Container::HeaderBar {
|
.style(crate::theme::Container::HeaderBar {
|
||||||
focused: self.focused,
|
focused: self.focused,
|
||||||
|
|
@ -326,9 +337,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
let mut title = Cow::default();
|
let mut title = Cow::default();
|
||||||
std::mem::swap(&mut title, &mut self.title);
|
std::mem::swap(&mut title, &mut self.title);
|
||||||
|
|
||||||
widget::text(title)
|
widget::text::heading(title)
|
||||||
.size(16)
|
|
||||||
.font(crate::font::FONT_SEMIBOLD)
|
|
||||||
.apply(widget::container)
|
.apply(widget::container)
|
||||||
.center_x()
|
.center_x()
|
||||||
.center_y()
|
.center_y()
|
||||||
|
|
@ -349,6 +358,13 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
.on_press(on_press)
|
.on_press(on_press)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let density = self.density.unwrap_or_else(crate::config::header_size);
|
||||||
|
let spacing = if matches!(density, Density::Compact) {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
8
|
||||||
|
};
|
||||||
|
|
||||||
widget::row::with_capacity(3)
|
widget::row::with_capacity(3)
|
||||||
.push_maybe(self.on_minimize.take().map(|m| {
|
.push_maybe(self.on_minimize.take().map(|m| {
|
||||||
icon(
|
icon(
|
||||||
|
|
@ -371,7 +387,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
m,
|
m,
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
.spacing(8)
|
.spacing(spacing)
|
||||||
.apply(widget::container)
|
.apply(widget::container)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.center_y()
|
.center_y()
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ impl Icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn into_element<Message: 'static>(self) -> Element<'static, Message> {
|
fn view<'a, Message: 'a>(self) -> Element<'a, Message> {
|
||||||
let from_image = |handle| {
|
let from_image = |handle| {
|
||||||
Image::new(handle)
|
Image::new(handle)
|
||||||
.width(
|
.width(
|
||||||
|
|
@ -120,8 +120,8 @@ impl Icon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message: 'static> From<Icon> for Element<'static, Message> {
|
impl<'a, Message: 'a> From<Icon> for Element<'a, Message> {
|
||||||
fn from(icon: Icon) -> Self {
|
fn from(icon: Icon) -> Self {
|
||||||
icon.into_element::<Message>()
|
icon.view::<Message>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
207
src/widget/id_container.rs
Normal file
207
src/widget/id_container.rs
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
use iced_core::event::{self, Event};
|
||||||
|
use iced_core::layout;
|
||||||
|
use iced_core::mouse;
|
||||||
|
use iced_core::overlay;
|
||||||
|
use iced_core::renderer;
|
||||||
|
use iced_core::widget::{Id, Tree};
|
||||||
|
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Widget};
|
||||||
|
pub use iced_style::container::{Appearance, StyleSheet};
|
||||||
|
|
||||||
|
pub fn id_container<'a, Message: 'static, Theme, E>(
|
||||||
|
content: E,
|
||||||
|
id: Id,
|
||||||
|
) -> IdContainer<'a, Message, Theme, crate::Renderer>
|
||||||
|
where
|
||||||
|
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
|
||||||
|
Theme: iced_style::container::StyleSheet,
|
||||||
|
<Theme as iced_widget::container::StyleSheet>::Style: From<crate::theme::Container>,
|
||||||
|
{
|
||||||
|
IdContainer::new(content, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An element decorating some content.
|
||||||
|
///
|
||||||
|
/// It is normally used for alignment purposes.
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct IdContainer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_core::Renderer,
|
||||||
|
{
|
||||||
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
|
id: Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> IdContainer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_core::Renderer,
|
||||||
|
{
|
||||||
|
/// Creates an empty [`IdContainer`].
|
||||||
|
pub(crate) fn new<T>(content: T, id: Id) -> Self
|
||||||
|
where
|
||||||
|
T: Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
{
|
||||||
|
IdContainer {
|
||||||
|
content: content.into(),
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
for IdContainer<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: iced_core::Renderer,
|
||||||
|
{
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
vec![Tree::new(&self.content)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
|
tree.children[0].diff(&mut self.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> iced_core::Size<Length> {
|
||||||
|
self.content.as_widget().size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
renderer: &Renderer,
|
||||||
|
limits: &layout::Limits,
|
||||||
|
) -> layout::Node {
|
||||||
|
let node = self
|
||||||
|
.content
|
||||||
|
.as_widget()
|
||||||
|
.layout(&mut tree.children[0], renderer, limits);
|
||||||
|
let size = node.size();
|
||||||
|
layout::Node::with_children(size, vec![node])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operate(
|
||||||
|
&self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
operation: &mut dyn iced_core::widget::Operation<
|
||||||
|
iced_core::widget::OperationOutputWrapper<Message>,
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
self.content.as_widget().operate(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
renderer,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
tree: &mut Tree,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: mouse::Cursor,
|
||||||
|
renderer: &Renderer,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) -> event::Status {
|
||||||
|
self.content.as_widget_mut().on_event(
|
||||||
|
&mut tree.children[0],
|
||||||
|
event.clone(),
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
viewport,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_interaction(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> mouse::Interaction {
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
self.content.as_widget().mouse_interaction(
|
||||||
|
&tree.children[0],
|
||||||
|
content_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
tree: &Tree,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
theme: &Theme,
|
||||||
|
renderer_style: &renderer::Style,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: mouse::Cursor,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
self.content.as_widget().draw(
|
||||||
|
&tree.children[0],
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
renderer_style,
|
||||||
|
content_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay<'b>(
|
||||||
|
&'b mut self,
|
||||||
|
tree: &'b mut Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||||
|
self.content.as_widget_mut().overlay(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drag_destinations(
|
||||||
|
&self,
|
||||||
|
state: &Tree,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles,
|
||||||
|
) {
|
||||||
|
let content_layout = layout.children().next().unwrap();
|
||||||
|
self.content.as_widget().drag_destinations(
|
||||||
|
&state.children[0],
|
||||||
|
content_layout,
|
||||||
|
dnd_rectangles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<crate::widget::Id> {
|
||||||
|
Some(self.id.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: crate::widget::Id) {
|
||||||
|
self.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Theme, Renderer> From<IdContainer<'a, Message, Theme, Renderer>>
|
||||||
|
for Element<'a, Message, Theme, Renderer>
|
||||||
|
where
|
||||||
|
Message: 'a,
|
||||||
|
Renderer: 'a + iced_core::Renderer,
|
||||||
|
Theme: 'a,
|
||||||
|
{
|
||||||
|
fn from(c: IdContainer<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
|
||||||
|
Element::new(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -59,14 +59,17 @@ pub use action::MenuAction as Action;
|
||||||
|
|
||||||
mod flex;
|
mod flex;
|
||||||
pub mod key_bind;
|
pub mod key_bind;
|
||||||
|
pub use key_bind::KeyBind;
|
||||||
|
|
||||||
mod menu_bar;
|
mod menu_bar;
|
||||||
pub use menu_bar::MenuBar;
|
|
||||||
pub(crate) use menu_bar::MenuBarState;
|
pub(crate) use menu_bar::MenuBarState;
|
||||||
|
pub use menu_bar::{menu_bar as bar, MenuBar};
|
||||||
|
|
||||||
mod menu_inner;
|
mod menu_inner;
|
||||||
mod menu_tree;
|
mod menu_tree;
|
||||||
pub use menu_tree::{menu_items as items, menu_root as root, MenuItem as Item, MenuTree as Tree};
|
pub use menu_tree::{
|
||||||
|
menu_button, menu_items as items, menu_root as root, MenuItem as Item, MenuTree as Tree,
|
||||||
|
};
|
||||||
|
|
||||||
pub use crate::style::menu_bar::{Appearance, StyleSheet};
|
pub use crate::style::menu_bar::{Appearance, StyleSheet};
|
||||||
pub(crate) use menu_inner::Menu;
|
pub(crate) use menu_inner::Menu;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use crate::widget::segmented_button::Entity;
|
|
||||||
|
|
||||||
/// `MenuAction` is a trait that represents an action in a menu.
|
/// `MenuAction` is a trait that represents an action in a menu.
|
||||||
///
|
///
|
||||||
/// It is used to define the behavior of menu items when they are activated.
|
/// It is used to define the behavior of menu items when they are activated.
|
||||||
|
|
@ -31,7 +29,7 @@ use crate::widget::segmented_button::Entity;
|
||||||
/// impl MenuAction for MyAction {
|
/// impl MenuAction for MyAction {
|
||||||
/// type Message = MyMessage;
|
/// type Message = MyMessage;
|
||||||
///
|
///
|
||||||
/// fn message(&self, entity: Option<Entity>) -> Self::Message {
|
/// fn message(&self) -> Self::Message {
|
||||||
/// match self {
|
/// match self {
|
||||||
/// MyAction::Open => MyMessage::Open,
|
/// MyAction::Open => MyMessage::Open,
|
||||||
/// MyAction::Save => MyMessage::Save,
|
/// MyAction::Save => MyMessage::Save,
|
||||||
|
|
@ -46,12 +44,8 @@ pub trait MenuAction: Clone + Copy + Eq + PartialEq {
|
||||||
|
|
||||||
/// Returns a message of type `Self::Message` when the action is triggered.
|
/// Returns a message of type `Self::Message` when the action is triggered.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `entity` - An optional `Entity` that may be associated with the action.
|
|
||||||
///
|
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * `Self::Message` - The message that is produced when the action is triggered.
|
/// * `Self::Message` - The message that is produced when the action is triggered.
|
||||||
fn message(&self, entity: Option<Entity>) -> Self::Message;
|
fn message(&self) -> Self::Message;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@ use iced_widget::core::{
|
||||||
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget,
|
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing.
|
||||||
|
pub fn menu_bar<Message, Renderer: iced_core::Renderer>(
|
||||||
|
menu_roots: Vec<MenuTree<Message, Renderer>>,
|
||||||
|
) -> MenuBar<Message, Renderer> {
|
||||||
|
MenuBar::new(menu_roots)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct MenuBarState {
|
pub(crate) struct MenuBarState {
|
||||||
pub(crate) pressed: bool,
|
pub(crate) pressed: bool,
|
||||||
pub(crate) view_cursor: Cursor,
|
pub(crate) view_cursor: Cursor,
|
||||||
|
|
@ -56,8 +63,7 @@ impl Default for MenuBarState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `MenuBar` collects `MenuTree`s and handles
|
/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing.
|
||||||
/// all the layout, event processing and drawing
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct MenuBar<'a, Message, Renderer = crate::Renderer>
|
pub struct MenuBar<'a, Message, Renderer = crate::Renderer>
|
||||||
where
|
where
|
||||||
|
|
@ -384,8 +390,8 @@ where
|
||||||
},
|
},
|
||||||
shadow: Default::default(),
|
shadow: Default::default(),
|
||||||
};
|
};
|
||||||
let path_color = styling.path;
|
|
||||||
renderer.fill_quad(path_quad, path_color);
|
renderer.fill_quad(path_quad, styling.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -709,8 +709,8 @@ where
|
||||||
},
|
},
|
||||||
shadow: Shadow::default(),
|
shadow: Shadow::default(),
|
||||||
};
|
};
|
||||||
let path_color = styling.path;
|
|
||||||
r.fill_quad(path_quad, path_color);
|
r.fill_quad(path_quad, styling.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw item
|
// draw item
|
||||||
|
|
|
||||||
|
|
@ -141,12 +141,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This macro creates a button for a MenuTree.
|
pub fn menu_button<'a, Message: 'a>(
|
||||||
#[macro_export]
|
children: Vec<crate::Element<'a, Message>>,
|
||||||
macro_rules! menu_button {
|
) -> crate::widget::Button<'a, Message> {
|
||||||
($($x:expr),+ $(,)?) => (
|
|
||||||
widget::button(
|
widget::button(
|
||||||
widget::Row::with_children(vec![$($x.into()),+])
|
widget::Row::with_children(children)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
|
|
@ -155,7 +154,6 @@ macro_rules! menu_button {
|
||||||
.padding([4, 16])
|
.padding([4, 16])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.style(theme::Button::MenuItem)
|
.style(theme::Button::MenuItem)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a menu item that performs an action when selected or a separator between menu items.
|
/// Represents a menu item that performs an action when selected or a separator between menu items.
|
||||||
|
|
@ -225,7 +223,7 @@ where
|
||||||
Element<'a, Message, crate::Theme, Renderer>: From<widget::button::Button<'a, Message>>,
|
Element<'a, Message, crate::Theme, Renderer>: From<widget::button::Button<'a, Message>>,
|
||||||
{
|
{
|
||||||
fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String {
|
fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String {
|
||||||
for (key_bind, key_action) in key_binds.iter() {
|
for (key_bind, key_action) in key_binds {
|
||||||
if action == key_action {
|
if action == key_action {
|
||||||
return key_bind.to_string();
|
return key_bind.to_string();
|
||||||
}
|
}
|
||||||
|
|
@ -243,41 +241,48 @@ where
|
||||||
match item {
|
match item {
|
||||||
MenuItem::Button(label, action) => {
|
MenuItem::Button(label, action) => {
|
||||||
let key = find_key(&action, key_binds);
|
let key = find_key(&action, key_binds);
|
||||||
let menu_button = menu_button!(
|
let menu_button = menu_button(vec![
|
||||||
widget::text(label),
|
widget::text(label).into(),
|
||||||
widget::horizontal_space(Length::Fill),
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
widget::text(key),
|
widget::text(key).into(),
|
||||||
)
|
])
|
||||||
.on_press(action.message(None));
|
.on_press(action.message());
|
||||||
|
|
||||||
trees.push(MenuTree::<Message, Renderer>::new(menu_button));
|
trees.push(MenuTree::<Message, Renderer>::new(menu_button));
|
||||||
}
|
}
|
||||||
MenuItem::CheckBox(label, value, action) => {
|
MenuItem::CheckBox(label, value, action) => {
|
||||||
let key = find_key(&action, &key_binds);
|
let key = find_key(&action, key_binds);
|
||||||
trees.push(MenuTree::new(
|
trees.push(MenuTree::new(
|
||||||
menu_button!(
|
menu_button(vec![
|
||||||
if value {
|
if value {
|
||||||
// TODO: add a object-select-symbolic icon, `Message: 'static` is required when using an icon widget.
|
widget::icon::from_name("object-select-symbolic")
|
||||||
widget::container(widget::text("✓"))
|
.size(16)
|
||||||
|
.icon()
|
||||||
|
.width(Length::Fixed(16.0))
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
widget::container(widget::Space::with_width(Length::Fixed(16.0)))
|
widget::Space::with_width(Length::Fixed(17.0)).into()
|
||||||
},
|
},
|
||||||
widget::Space::with_width(Length::Fixed(8.0)),
|
widget::Space::with_width(Length::Fixed(8.0)).into(),
|
||||||
widget::text(label),
|
widget::text(label)
|
||||||
widget::horizontal_space(Length::Fill),
|
.horizontal_alignment(iced::alignment::Horizontal::Left)
|
||||||
widget::text(key)
|
.into(),
|
||||||
)
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
.on_press(action.message(None)),
|
widget::text(key).into(),
|
||||||
|
])
|
||||||
|
.on_press(action.message()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
MenuItem::Folder(label, children) => {
|
MenuItem::Folder(label, children) => {
|
||||||
trees.push(MenuTree::<Message, Renderer>::with_children(
|
trees.push(MenuTree::<Message, Renderer>::with_children(
|
||||||
menu_button!(
|
menu_button(vec![
|
||||||
widget::text(label),
|
widget::text(label).into(),
|
||||||
widget::horizontal_space(Length::Fill),
|
widget::horizontal_space(Length::Fill).into(),
|
||||||
// TODO: add a pan-end-symbolic icon, `Message: 'static` is required when using an icon widget.
|
widget::icon::from_name("pan-end-symbolic")
|
||||||
widget::text("▶"),
|
.size(16)
|
||||||
),
|
.icon()
|
||||||
|
.into(),
|
||||||
|
]),
|
||||||
menu_items(key_binds, children),
|
menu_items(key_binds, children),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,70 +1,160 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
//! Cosmic-themed widget implementations.
|
//! The COSMIC widget library
|
||||||
|
//!
|
||||||
|
//! This module contains a wide variety of widgets used throughout the COSMIC app ecosystem.
|
||||||
|
//!
|
||||||
|
//! # Overview
|
||||||
|
//!
|
||||||
|
//! Add widgets to your application view by calling the modules and functions below.
|
||||||
|
//! Widgets are constructed by chaining their property methods using a functional paradigm.
|
||||||
|
//! Modules may contain additional functions for constructing different variations of a widget.
|
||||||
|
//! Each module will typically have one widget with the same name as the module, which will be re-exported here.
|
||||||
|
//!
|
||||||
|
//! ```no_run,ignore
|
||||||
|
//! use cosmic::prelude::*;
|
||||||
|
//! use cosmic::{cosmic_theme, theme, widget};
|
||||||
|
//!
|
||||||
|
//! const REPOSITORY: &str = "https://github.com/pop-os/libcosmic";
|
||||||
|
//!
|
||||||
|
//! let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
//!
|
||||||
|
//! let link = widget::button::link(REPOSITORY)
|
||||||
|
//! .on_press(Message::LaunchUrl(REPOSITORY))
|
||||||
|
//! .padding(0);
|
||||||
|
//!
|
||||||
|
//! let content = widget::column()
|
||||||
|
//! .push(widget::icon::from_name("my-app-icon"))
|
||||||
|
//! .push(widget::text::title3("My App Name"))
|
||||||
|
//! .push(link)
|
||||||
|
//! .align_items(Alignment::Center)
|
||||||
|
//! .spacing(space_xxs);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Widgets may borrow data from your application struct, and should do so to avoid allocating.
|
||||||
|
//!
|
||||||
|
//! ```no_run,ignore
|
||||||
|
//! let text = widget::text::body(&self.cached_text);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Use the [`cosmic::Apply`](crate::Apply) trait to embed widgets into other widgets which accept them.
|
||||||
|
//!
|
||||||
|
//! ```no_run,ignore
|
||||||
|
//! let button = widget::icon::from_name("printer-symbolic")
|
||||||
|
//! .apply(widget::button::icon)
|
||||||
|
//! .on_press(Message::Print);
|
||||||
|
//! ```
|
||||||
|
|
||||||
// Re-exports from Iced
|
// Re-exports from Iced
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use iced::widget::{canvas, Canvas};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{checkbox, Checkbox};
|
pub use iced::widget::{checkbox, Checkbox};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{combo_box, ComboBox};
|
pub use iced::widget::{combo_box, ComboBox};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{container, Container};
|
pub use iced::widget::{container, Container};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{horizontal_space, space, vertical_space, Space};
|
pub use iced::widget::{horizontal_space, space, vertical_space, Space};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{image, Image};
|
pub use iced::widget::{image, Image};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{lazy, Lazy};
|
pub use iced::widget::{lazy, Lazy};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{mouse_area, MouseArea};
|
pub use iced::widget::{mouse_area, MouseArea};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{pane_grid, PaneGrid};
|
pub use iced::widget::{pane_grid, PaneGrid};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{progress_bar, ProgressBar};
|
pub use iced::widget::{progress_bar, ProgressBar};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{radio, Radio};
|
pub use iced::widget::{radio, Radio};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{responsive, Responsive};
|
pub use iced::widget::{responsive, Responsive};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{slider, vertical_slider, Slider, VerticalSlider};
|
pub use iced::widget::{slider, vertical_slider, Slider, VerticalSlider};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced::widget::{svg, Svg};
|
pub use iced::widget::{svg, Svg};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use iced::widget::{text_editor, TextEditor};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use iced_core::widget::{Id, Operation, Widget};
|
pub use iced_core::widget::{Id, Operation, Widget};
|
||||||
|
|
||||||
pub mod aspect_ratio;
|
pub mod aspect_ratio;
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
|
#[doc(inline)]
|
||||||
pub use button::{button, Button, IconButton, LinkButton, TextButton};
|
pub use button::{button, Button, IconButton, LinkButton, TextButton};
|
||||||
|
|
||||||
pub(crate) mod common;
|
pub(crate) mod common;
|
||||||
|
|
||||||
pub mod calendar;
|
pub mod calendar;
|
||||||
|
#[doc(inline)]
|
||||||
pub use calendar::{calendar, Calendar};
|
pub use calendar::{calendar, Calendar};
|
||||||
|
|
||||||
pub mod card;
|
pub mod card;
|
||||||
|
#[doc(inline)]
|
||||||
pub use card::*;
|
pub use card::*;
|
||||||
|
|
||||||
pub mod color_picker;
|
pub mod color_picker;
|
||||||
|
#[doc(inline)]
|
||||||
pub use color_picker::{ColorPicker, ColorPickerModel};
|
pub use color_picker::{ColorPicker, ColorPickerModel};
|
||||||
|
|
||||||
pub mod context_drawer;
|
pub mod context_drawer;
|
||||||
|
#[doc(inline)]
|
||||||
pub use context_drawer::{context_drawer, ContextDrawer};
|
pub use context_drawer::{context_drawer, ContextDrawer};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use column::{column, Column};
|
pub use column::{column, Column};
|
||||||
pub mod column {
|
pub mod column {
|
||||||
|
//! A container which aligns its children in a column.
|
||||||
|
|
||||||
pub type Column<'a, Message> = iced::widget::Column<'a, Message, crate::Theme, crate::Renderer>;
|
pub type Column<'a, Message> = iced::widget::Column<'a, Message, crate::Theme, crate::Renderer>;
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
/// A container which aligns its children in a column.
|
||||||
pub fn column<'a, Message>() -> Column<'a, Message> {
|
pub fn column<'a, Message>() -> Column<'a, Message> {
|
||||||
Column::new()
|
Column::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
/// A pre-allocated [`column`].
|
||||||
pub fn with_capacity<'a, Message>(capacity: usize) -> Column<'a, Message> {
|
pub fn with_capacity<'a, Message>(capacity: usize) -> Column<'a, Message> {
|
||||||
Column::with_children(Vec::with_capacity(capacity))
|
Column::with_children(Vec::with_capacity(capacity))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
/// A [`column`] that will be assigned a [`Vec`] of children.
|
||||||
pub fn with_children<Message>(children: Vec<crate::Element<Message>>) -> Column<Message> {
|
pub fn with_children<Message>(children: Vec<crate::Element<Message>>) -> Column<Message> {
|
||||||
Column::with_children(children)
|
Column::with_children(children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod layer_container;
|
pub mod layer_container;
|
||||||
|
#[doc(inline)]
|
||||||
pub use layer_container::{layer_container, LayerContainer};
|
pub use layer_container::{layer_container, LayerContainer};
|
||||||
|
|
||||||
pub mod context_menu;
|
pub mod context_menu;
|
||||||
|
#[doc(inline)]
|
||||||
pub use context_menu::{context_menu, ContextMenu};
|
pub use context_menu::{context_menu, ContextMenu};
|
||||||
|
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
|
#[doc(inline)]
|
||||||
pub use dialog::{dialog, Dialog};
|
pub use dialog::{dialog, Dialog};
|
||||||
|
|
||||||
/// An element to distinguish a boundary between two elements.
|
/// An element to distinguish a boundary between two elements.
|
||||||
|
|
@ -117,67 +207,93 @@ pub mod divider {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod dnd_destination;
|
pub mod dnd_destination;
|
||||||
|
#[doc(inline)]
|
||||||
pub use dnd_destination::{dnd_destination, DndDestination};
|
pub use dnd_destination::{dnd_destination, DndDestination};
|
||||||
|
|
||||||
pub mod dnd_source;
|
pub mod dnd_source;
|
||||||
|
#[doc(inline)]
|
||||||
pub use dnd_source::{dnd_source, DndSource};
|
pub use dnd_source::{dnd_source, DndSource};
|
||||||
|
|
||||||
pub mod dropdown;
|
pub mod dropdown;
|
||||||
|
#[doc(inline)]
|
||||||
pub use dropdown::{dropdown, Dropdown};
|
pub use dropdown::{dropdown, Dropdown};
|
||||||
|
|
||||||
pub mod flex_row;
|
pub mod flex_row;
|
||||||
|
#[doc(inline)]
|
||||||
pub use flex_row::{flex_row, FlexRow};
|
pub use flex_row::{flex_row, FlexRow};
|
||||||
|
|
||||||
pub mod grid;
|
pub mod grid;
|
||||||
|
#[doc(inline)]
|
||||||
pub use grid::{grid, Grid};
|
pub use grid::{grid, Grid};
|
||||||
|
|
||||||
mod header_bar;
|
mod header_bar;
|
||||||
|
#[doc(inline)]
|
||||||
pub use header_bar::{header_bar, HeaderBar};
|
pub use header_bar::{header_bar, HeaderBar};
|
||||||
|
|
||||||
pub mod icon;
|
pub mod icon;
|
||||||
|
#[doc(inline)]
|
||||||
pub use icon::{icon, Icon};
|
pub use icon::{icon, Icon};
|
||||||
|
|
||||||
|
pub mod id_container;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use id_container::{id_container, IdContainer};
|
||||||
|
|
||||||
#[cfg(feature = "animated-image")]
|
#[cfg(feature = "animated-image")]
|
||||||
pub mod frames;
|
pub mod frames;
|
||||||
|
|
||||||
|
pub use taffy::JustifyContent;
|
||||||
|
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub use list::*;
|
#[doc(inline)]
|
||||||
|
pub use list::{list_column, ListColumn};
|
||||||
|
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
|
|
||||||
pub mod nav_bar;
|
pub mod nav_bar;
|
||||||
|
#[doc(inline)]
|
||||||
pub use nav_bar::{nav_bar, nav_bar_dnd};
|
pub use nav_bar::{nav_bar, nav_bar_dnd};
|
||||||
|
|
||||||
pub mod nav_bar_toggle;
|
pub mod nav_bar_toggle;
|
||||||
|
#[doc(inline)]
|
||||||
pub use nav_bar_toggle::{nav_bar_toggle, NavBarToggle};
|
pub use nav_bar_toggle::{nav_bar_toggle, NavBarToggle};
|
||||||
|
|
||||||
pub mod popover;
|
pub mod popover;
|
||||||
|
#[doc(inline)]
|
||||||
pub use popover::{popover, Popover};
|
pub use popover::{popover, Popover};
|
||||||
|
|
||||||
pub mod rectangle_tracker;
|
pub mod rectangle_tracker;
|
||||||
|
#[doc(inline)]
|
||||||
pub use rectangle_tracker::{rectangle_tracker, RectangleTracker};
|
pub use rectangle_tracker::{rectangle_tracker, RectangleTracker};
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use row::{row, Row};
|
pub use row::{row, Row};
|
||||||
|
|
||||||
pub mod row {
|
pub mod row {
|
||||||
|
//! A container which aligns its children in a row.
|
||||||
|
|
||||||
pub type Row<'a, Message> = iced::widget::Row<'a, Message, crate::Theme, crate::Renderer>;
|
pub type Row<'a, Message> = iced::widget::Row<'a, Message, crate::Theme, crate::Renderer>;
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
/// A container which aligns its children in a row.
|
||||||
pub fn row<'a, Message>() -> Row<'a, Message> {
|
pub fn row<'a, Message>() -> Row<'a, Message> {
|
||||||
Row::new()
|
Row::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
/// A pre-allocated [`row`].
|
||||||
pub fn with_capacity<'a, Message>(capacity: usize) -> Row<'a, Message> {
|
pub fn with_capacity<'a, Message>(capacity: usize) -> Row<'a, Message> {
|
||||||
Row::with_children(Vec::with_capacity(capacity))
|
Row::with_children(Vec::with_capacity(capacity))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
/// A [`row`] that will be assigned a [`Vec`] of children.
|
||||||
pub fn with_children<Message>(children: Vec<crate::Element<Message>>) -> Row<Message> {
|
pub fn with_children<Message>(children: Vec<crate::Element<Message>>) -> Row<Message> {
|
||||||
Row::with_children(children)
|
Row::with_children(children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod scrollable;
|
mod scrollable;
|
||||||
|
#[doc(inline)]
|
||||||
pub use scrollable::*;
|
pub use scrollable::*;
|
||||||
|
|
||||||
pub mod segmented_button;
|
pub mod segmented_button;
|
||||||
|
|
@ -186,19 +302,26 @@ pub mod segmented_control;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
pub mod spin_button;
|
pub mod spin_button;
|
||||||
|
#[doc(inline)]
|
||||||
pub use spin_button::{spin_button, SpinButton};
|
pub use spin_button::{spin_button, SpinButton};
|
||||||
|
|
||||||
pub mod tab_bar;
|
pub mod tab_bar;
|
||||||
|
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
#[doc(inline)]
|
||||||
pub use text::{text, Text};
|
pub use text::{text, Text};
|
||||||
|
|
||||||
pub mod text_input;
|
pub mod text_input;
|
||||||
pub use text_input::*;
|
#[doc(inline)]
|
||||||
|
pub use text_input::{
|
||||||
|
editable_input, inline_input, search_input, secure_input, text_input, TextInput,
|
||||||
|
};
|
||||||
|
|
||||||
mod toggler;
|
mod toggler;
|
||||||
|
#[doc(inline)]
|
||||||
pub use toggler::toggler;
|
pub use toggler::toggler;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
pub use tooltip::{tooltip, Tooltip};
|
pub use tooltip::{tooltip, Tooltip};
|
||||||
pub mod tooltip {
|
pub mod tooltip {
|
||||||
use crate::Element;
|
use crate::Element;
|
||||||
|
|
@ -224,4 +347,5 @@ pub mod tooltip {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod warning;
|
pub mod warning;
|
||||||
|
#[doc(inline)]
|
||||||
pub use warning::*;
|
pub use warning::*;
|
||||||
|
|
|
||||||
|
|
@ -42,13 +42,9 @@ impl<'a, Message: 'static + Clone> From<NavBarToggle<Message>> for Element<'a, M
|
||||||
};
|
};
|
||||||
|
|
||||||
widget::button::icon(icon)
|
widget::button::icon(icon)
|
||||||
.padding([8, 16, 8, 16])
|
|
||||||
.on_press_maybe(nav_bar_toggle.on_toggle)
|
.on_press_maybe(nav_bar_toggle.on_toggle)
|
||||||
.selected(nav_bar_toggle.selected)
|
.selected(nav_bar_toggle.selected)
|
||||||
.style(nav_bar_toggle.style)
|
.style(nav_bar_toggle.style)
|
||||||
.apply(widget::container)
|
|
||||||
.center_y()
|
|
||||||
.height(Length::Fill)
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ where
|
||||||
/// .build()
|
/// .build()
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
|
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
|
||||||
pub fn icon(mut self, icon: Icon) -> Self {
|
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||||
self.model.0.icon_set(self.id, icon);
|
self.model.0.icon_set(self.id, icon.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,8 @@ where
|
||||||
/// model.insert().text("Item A").icon(IconSource::from("icon-a"));
|
/// model.insert().text("Item A").icon(IconSource::from("icon-a"));
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
|
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
|
||||||
pub fn icon(self, icon: Icon) -> Self {
|
pub fn icon(self, icon: impl Into<Icon>) -> Self {
|
||||||
self.model.icon_set(self.id, icon);
|
self.model.icon_set(self.id, icon.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
widget::{column, horizontal_space, row, text, Row},
|
widget::{column, container, flex_row, horizontal_space, row, text, FlexRow, Row},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
|
use iced::alignment::Horizontal;
|
||||||
use iced_core::Length;
|
use iced_core::Length;
|
||||||
|
use taffy::AlignContent;
|
||||||
|
|
||||||
/// A settings item aligned in a row
|
/// A settings item aligned in a row
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
@ -29,9 +31,33 @@ pub fn item<'a, Message: 'static>(
|
||||||
#[allow(clippy::module_name_repetitions)]
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message> {
|
pub fn item_row<Message>(children: Vec<Element<Message>>) -> Row<Message> {
|
||||||
row::with_children(children)
|
row::with_children(children)
|
||||||
|
.spacing(12)
|
||||||
.align_items(iced::Alignment::Center)
|
.align_items(iced::Alignment::Center)
|
||||||
.padding([0, 18])
|
.padding([0, 18])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A settings item aligned in a flex row
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub fn flex_item<'a, Message: 'static>(
|
||||||
|
title: impl Into<Cow<'a, str>> + 'a,
|
||||||
|
widget: impl Into<Element<'a, Message>> + 'a,
|
||||||
|
) -> FlexRow<'a, Message> {
|
||||||
|
flex_item_row(vec![
|
||||||
|
text(title).width(Length::Fill).into(),
|
||||||
|
container(widget).into(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A settings item aligned in a flex row
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub fn flex_item_row<Message>(children: Vec<Element<Message>>) -> FlexRow<Message> {
|
||||||
|
flex_row(children)
|
||||||
|
.padding([0, 18])
|
||||||
.spacing(12)
|
.spacing(12)
|
||||||
|
.min_item_width(200.0)
|
||||||
|
.justify_items(iced::Alignment::Center)
|
||||||
|
.justify_content(AlignContent::SpaceBetween)
|
||||||
|
.width(Length::Fill)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a builder for an item, beginning with the title.
|
/// Creates a builder for an item, beginning with the title.
|
||||||
|
|
@ -61,6 +87,15 @@ pub struct Item<'a, Message> {
|
||||||
impl<'a, Message: 'static> Item<'a, Message> {
|
impl<'a, Message: 'static> Item<'a, Message> {
|
||||||
/// Assigns a control to the item.
|
/// Assigns a control to the item.
|
||||||
pub fn control(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message> {
|
pub fn control(self, widget: impl Into<Element<'a, Message>>) -> Row<'a, Message> {
|
||||||
|
item_row(self.control_(widget))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assigns a control which flexes.
|
||||||
|
pub fn flex_control(self, widget: impl Into<Element<'a, Message>>) -> FlexRow<'a, Message> {
|
||||||
|
flex_item_row(self.control_(widget))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_(self, widget: impl Into<Element<'a, Message>>) -> Vec<Element<'a, Message>> {
|
||||||
let mut contents = Vec::with_capacity(4);
|
let mut contents = Vec::with_capacity(4);
|
||||||
|
|
||||||
if let Some(icon) = self.icon {
|
if let Some(icon) = self.icon {
|
||||||
|
|
@ -80,15 +115,14 @@ impl<'a, Message: 'static> Item<'a, Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
contents.push(widget.into());
|
contents.push(widget.into());
|
||||||
|
contents
|
||||||
item_row(contents)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggler(
|
pub fn toggler(
|
||||||
self,
|
self,
|
||||||
is_checked: bool,
|
is_checked: bool,
|
||||||
message: impl Fn(bool) -> Message + 'static,
|
message: impl Fn(bool) -> Message + 'static,
|
||||||
) -> Row<'a, Message> {
|
) -> FlexRow<'a, Message> {
|
||||||
self.control(crate::widget::toggler(None, is_checked, message))
|
self.flex_control(crate::widget::toggler(None, is_checked, message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
pub mod item;
|
pub mod item;
|
||||||
mod section;
|
mod section;
|
||||||
|
|
||||||
pub use self::item::{item, item_row};
|
pub use self::item::{flex_item, flex_item_row, item, item_row};
|
||||||
pub use self::section::{view_section, Section};
|
pub use self::section::{view_section, Section};
|
||||||
|
|
||||||
use crate::widget::{column, Column};
|
use crate::widget::{column, Column};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2022 System76 <info@system76.com>
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! A control for incremental adjustments of a value.
|
||||||
|
|
||||||
mod model;
|
mod model;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|
@ -20,6 +22,7 @@ pub struct SpinButton<'a, Message> {
|
||||||
on_change: Box<dyn Fn(model::Message) -> Message + 'static>,
|
on_change: Box<dyn Fn(model::Message) -> Message + 'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A control for incremental adjustments of a value.
|
||||||
pub fn spin_button<'a, Message: 'static>(
|
pub fn spin_button<'a, Message: 'static>(
|
||||||
label: impl Into<Cow<'a, str>>,
|
label: impl Into<Cow<'a, str>>,
|
||||||
on_change: impl Fn(model::Message) -> Message + 'static,
|
on_change: impl Fn(model::Message) -> Message + 'static,
|
||||||
|
|
|
||||||
|
|
@ -729,6 +729,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tree.children.len() > 0 {
|
||||||
let index = tree.children.len() - 1;
|
let index = tree.children.len() - 1;
|
||||||
if let (Some(trailing_icon), Some(tree)) =
|
if let (Some(trailing_icon), Some(tree)) =
|
||||||
(self.trailing_icon.as_mut(), tree.children.get_mut(index))
|
(self.trailing_icon.as_mut(), tree.children.get_mut(index))
|
||||||
|
|
@ -755,6 +756,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update(
|
update(
|
||||||
event,
|
event,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue