chore: various fixes and some cleanup
This commit is contained in:
parent
e10459fb37
commit
e8d53b14ea
27 changed files with 1181 additions and 107 deletions
22
Cargo.toml
22
Cargo.toml
|
|
@ -8,27 +8,28 @@ rust-version = "1.90"
|
|||
name = "cosmic"
|
||||
|
||||
[features]
|
||||
# default = ["dbus-config", "multi-window", "a11y"]
|
||||
default = [ "debug",
|
||||
default = [
|
||||
"winit",
|
||||
"tokio",
|
||||
# "xdg-portal",
|
||||
"a11y",
|
||||
"wgpu",
|
||||
"single-instance",
|
||||
"surface-message",
|
||||
"a11y",
|
||||
"dbus-config",
|
||||
"x11",
|
||||
"wayland",
|
||||
"multi-window",
|
||||
"about","animated-image","autosize", "dbus-config", "pipewire", "process", "rfd", "desktop", "desktop-systemd-scope", "serde-keycode", "qr_code", "markdown", "highlighter"
|
||||
]
|
||||
] # default = ["dbus-config", "multi-window", "a11y"]
|
||||
# Accessibility support
|
||||
a11y = ["iced/a11y", "iced_accessibility"]
|
||||
# Enable about widget
|
||||
about = []
|
||||
# Builds support for animated images
|
||||
animated-image = ["dep:async-fs", "image/gif", "image/webp", "image/png", "tokio?/io-util", "tokio?/fs"]
|
||||
animated-image = [
|
||||
"dep:async-fs",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"image/png",
|
||||
"tokio?/io-util",
|
||||
"tokio?/fs",
|
||||
]
|
||||
# XXX autosize should not be used on winit windows unless dialogs
|
||||
autosize = []
|
||||
applet = [
|
||||
|
|
@ -155,6 +156,7 @@ tracing = "0.1.44"
|
|||
unicode-segmentation = "1.12"
|
||||
url = "2.5.8"
|
||||
zbus = { version = "5.13.2", default-features = false, optional = true }
|
||||
float-cmp = "0.10.0"
|
||||
|
||||
# Enable DBus feature on Linux targets
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ env_logger = "0.10.2"
|
|||
log = "0.4.29"
|
||||
|
||||
[dependencies.libcosmic]
|
||||
# path = "../../"
|
||||
branch = "iced-rebase"
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
default-features = false
|
||||
features = ["applet-token"]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub struct Window {
|
|||
core: Core,
|
||||
popup: Option<Id>,
|
||||
example_row: bool,
|
||||
toggle: bool,
|
||||
selected: Option<usize>,
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ impl Default for Window {
|
|||
core: Core::default(),
|
||||
popup: None,
|
||||
example_row: false,
|
||||
toggle: false,
|
||||
selected: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +35,7 @@ pub enum Message {
|
|||
ToggleExampleRow(bool),
|
||||
Selected(usize),
|
||||
Surface(cosmic::surface::Action),
|
||||
Toggle(bool),
|
||||
}
|
||||
|
||||
impl cosmic::Application for Window {
|
||||
|
|
@ -71,7 +74,6 @@ impl cosmic::Application for Window {
|
|||
Message::ToggleExampleRow(toggled) => {
|
||||
self.example_row = toggled;
|
||||
}
|
||||
|
||||
Message::Surface(a) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
|
|
@ -80,6 +82,9 @@ impl cosmic::Application for Window {
|
|||
Message::Selected(i) => {
|
||||
self.selected = Some(i);
|
||||
}
|
||||
Message::Toggle(v) => {
|
||||
self.toggle = v;
|
||||
}
|
||||
};
|
||||
Task::none()
|
||||
}
|
||||
|
|
@ -123,9 +128,9 @@ impl cosmic::Application for Window {
|
|||
"Example row",
|
||||
cosmic::widget::container(
|
||||
toggler(state.example_row)
|
||||
.on_toggle(|value| Message::ToggleExampleRow(value)),
|
||||
)
|
||||
.height(Length::Fixed(50.)),
|
||||
.on_toggle(Message::ToggleExampleRow)
|
||||
.width(Length::Fill),
|
||||
),
|
||||
))
|
||||
.add(popup_dropdown(
|
||||
&["1", "asdf", "hello", "test"],
|
||||
|
|
@ -155,7 +160,7 @@ impl cosmic::Application for Window {
|
|||
"oops".into()
|
||||
}
|
||||
|
||||
fn style(&self) -> Option<cosmic::iced_runtime::Appearance> {
|
||||
fn style(&self) -> Option<cosmic::iced_core::theme::Style> {
|
||||
Some(cosmic::applet::style())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,11 @@ default = ["wayland"]
|
|||
wayland = ["libcosmic/wayland"]
|
||||
|
||||
[dependencies]
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = "0.3.22"
|
||||
tracing-log = "0.2.0"
|
||||
env_logger = "0.11"
|
||||
|
||||
[dependencies.libcosmic]
|
||||
path = "../../"
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
branch = "iced-rebase"
|
||||
features = [
|
||||
"debug",
|
||||
"winit",
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ impl widget::menu::Action for Action {
|
|||
/// Runs application with these settings
|
||||
#[rustfmt::skip]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// tracing_subscriber::fmt::init();
|
||||
// let _ = tracing_log::LogTracer::init();
|
||||
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
|
||||
let input = vec![
|
||||
(Page::Page1, "🖖 Hello from libcosmic.".into()),
|
||||
|
|
@ -66,9 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
let settings = Settings::default()
|
||||
.size(Size::new(1024., 768.));
|
||||
|
||||
cosmic::app::run::<App>(settings, input)?;
|
||||
|
||||
cosmic::app::run::<App>(settings, input).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
51
src/anim.rs
Normal file
51
src/anim.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
/// A simple linear interpolation calculation function.
|
||||
/// p = `percent_complete` in decimal form
|
||||
#[must_use]
|
||||
pub fn lerp(start: f32, end: f32, p: f32) -> f32 {
|
||||
(1.0 - p) * start + p * end
|
||||
}
|
||||
|
||||
/// A fast smooth interpolation calculation function.
|
||||
/// p = `percent_complete` in decimal form
|
||||
#[must_use]
|
||||
pub fn slerp(start: f32, end: f32, p: f32) -> f32 {
|
||||
let t = smootherstep(p);
|
||||
(1.0 - t) * start + t * end
|
||||
}
|
||||
|
||||
/// utility function which maps a value [0, 1] -> [0, 1] using the smootherstep function
|
||||
pub fn smootherstep(t: f32) -> f32 {
|
||||
(6.0 * t.powi(5) - 15.0 * t.powi(4) + 10.0 * t.powi(3)).clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct State {
|
||||
pub last_change: Option<Instant>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn changed(&mut self, dur: Duration) {
|
||||
let t = self.t(dur, false);
|
||||
let diff = dur.mul_f32(t.abs());
|
||||
let now = Instant::now();
|
||||
self.last_change = Some(now.checked_sub(diff).unwrap_or(now));
|
||||
}
|
||||
|
||||
pub fn anim_done(&mut self, dur: Duration) {
|
||||
if self
|
||||
.last_change
|
||||
.is_some_and(|t| Instant::now().duration_since(t) > dur)
|
||||
{
|
||||
self.last_change = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn t(&self, dur: Duration, forward: bool) -> f32 {
|
||||
let res = self.last_change.map_or(1., |t| {
|
||||
Instant::now().duration_since(t).as_millis() as f32 / dur.as_millis() as f32
|
||||
});
|
||||
if forward { res } else { 1. - res }
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ use cosmic_config::CosmicConfigEntry;
|
|||
pub mod context_drawer;
|
||||
pub use context_drawer::{ContextDrawer, context_drawer};
|
||||
use iced::application::BootFn;
|
||||
use iced_core::Widget;
|
||||
pub mod cosmic;
|
||||
pub mod settings;
|
||||
|
||||
|
|
@ -93,6 +94,7 @@ pub(crate) fn iced_settings<App: Application>(
|
|||
pub(crate) struct BootDataInner<A: crate::app::Application> {
|
||||
pub flags: A::Flags,
|
||||
pub core: Core,
|
||||
pub settings: window::Settings,
|
||||
}
|
||||
|
||||
pub(crate) struct BootData<A: crate::app::Application>(pub Rc<RefCell<Option<BootDataInner<A>>>>);
|
||||
|
|
@ -102,8 +104,23 @@ impl<A: crate::app::Application> BootFn<cosmic::Cosmic<A>, crate::Action<A::Mess
|
|||
{
|
||||
fn boot(&self) -> (cosmic::Cosmic<A>, iced::Task<crate::Action<A::Message>>) {
|
||||
let mut data = self.0.borrow_mut();
|
||||
let data = data.take().unwrap();
|
||||
cosmic::Cosmic::<A>::init((data.core, data.flags))
|
||||
let mut data = data.take().unwrap();
|
||||
let mut tasks = Vec::new();
|
||||
#[cfg(feature = "multi-window")]
|
||||
if data.core.main_window_id().is_some() {
|
||||
let window_task = iced_runtime::task::oneshot(|channel| {
|
||||
iced_runtime::Action::Window(iced_runtime::window::Action::Open(
|
||||
window::Id::RESERVED,
|
||||
data.settings,
|
||||
channel,
|
||||
))
|
||||
});
|
||||
data.core.set_main_window_id(Some(window::Id::RESERVED));
|
||||
tasks.push(window_task.discard());
|
||||
}
|
||||
let (a, t) = cosmic::Cosmic::<A>::init((data.core, data.flags));
|
||||
tasks.push(t);
|
||||
(a, Task::batch(tasks))
|
||||
}
|
||||
}
|
||||
/// Launch a COSMIC application with the given [`Settings`].
|
||||
|
|
@ -127,6 +144,7 @@ pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Res
|
|||
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||
flags,
|
||||
core,
|
||||
settings: window_settings.clone(),
|
||||
})))),
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
|
|
@ -147,10 +165,11 @@ pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Res
|
|||
// app = app.window(window_settings);
|
||||
core.main_window = Some(iced_core::window::Id::RESERVED);
|
||||
}
|
||||
let mut app = iced::daemon(
|
||||
let app = iced::daemon(
|
||||
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||
flags,
|
||||
core,
|
||||
settings: window_settings,
|
||||
})))),
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
|
|
@ -240,6 +259,7 @@ where
|
|||
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||
flags,
|
||||
core,
|
||||
settings: window_settings.clone(),
|
||||
})))),
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
|
|
@ -263,6 +283,7 @@ where
|
|||
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||
flags,
|
||||
core,
|
||||
settings: window_settings,
|
||||
})))),
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
|
|
@ -700,7 +721,7 @@ impl<App: Application> ApplicationExt for App {
|
|||
[0, 0, 0, 0]
|
||||
})
|
||||
.into(),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
//TODO: this element is added to workaround state issues
|
||||
widgets.push(space::horizontal().width(Length::Shrink).into());
|
||||
|
|
@ -710,6 +731,7 @@ impl<App: Application> ApplicationExt for App {
|
|||
|
||||
widgets
|
||||
});
|
||||
|
||||
let content_col = crate::widget::column::with_capacity(2)
|
||||
.push(content_row)
|
||||
.push_maybe(self.footer().map(|footer| {
|
||||
|
|
|
|||
|
|
@ -392,7 +392,7 @@ impl Context {
|
|||
}
|
||||
}),
|
||||
)
|
||||
.width(Length::Shrink)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Shrink)
|
||||
.align_x(horizontal_align)
|
||||
.align_y(vertical_align),
|
||||
|
|
@ -584,6 +584,7 @@ pub fn run<App: Application>(flags: App::Flags) -> iced::Result {
|
|||
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
|
||||
flags,
|
||||
core,
|
||||
settings: window_settings,
|
||||
})))),
|
||||
cosmic::Cosmic::update,
|
||||
cosmic::Cosmic::view,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub use apply::{Also, Apply};
|
|||
pub mod action;
|
||||
pub use action::Action;
|
||||
|
||||
pub mod anim;
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
pub mod app;
|
||||
#[cfg(feature = "winit")]
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.content);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
|
|
@ -147,7 +147,7 @@ where
|
|||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
tree,
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
|
|
@ -193,7 +193,7 @@ where
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
|
|||
|
|
@ -357,7 +357,6 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
operation,
|
||||
);
|
||||
});
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
|
|
|||
587
src/widget/cards.rs
Normal file
587
src/widget/cards.rs
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
//! An expandable stack of cards
|
||||
use std::time::Duration;
|
||||
|
||||
use self::iced_core::{
|
||||
Element, Event, Length, Size, Vector, Widget, border::Radius, id::Id, layout::Node,
|
||||
renderer::Quad, widget::Tree,
|
||||
};
|
||||
use crate::{
|
||||
anim,
|
||||
iced_core::{self, Border, Shadow},
|
||||
widget::{
|
||||
button,
|
||||
card::style::Style,
|
||||
column,
|
||||
icon::{self, Handle},
|
||||
row, text,
|
||||
},
|
||||
};
|
||||
use float_cmp::approx_eq;
|
||||
use iced::widget;
|
||||
use iced_core::{widget::tree, window};
|
||||
|
||||
const ICON_SIZE: u16 = 16;
|
||||
const TOP_SPACING: u16 = 4;
|
||||
const VERTICAL_SPACING: f32 = 8.0;
|
||||
const PADDING: u16 = 16;
|
||||
const BG_CARD_VISIBLE_HEIGHT: f32 = 4.0;
|
||||
const BG_CARD_BORDER_RADIUS: f32 = 8.0;
|
||||
const BG_CARD_MARGIN_STEP: f32 = 8.0;
|
||||
|
||||
/// get an expandable stack of cards
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn cards<'a, Message, F, G>(
|
||||
id: widget::Id,
|
||||
card_inner_elements: Vec<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
||||
on_clear_all: Message,
|
||||
on_show_more: Option<F>,
|
||||
on_activate: Option<G>,
|
||||
show_more_label: &'a str,
|
||||
show_less_label: &'a str,
|
||||
clear_all_label: &'a str,
|
||||
show_less_icon: Option<Handle>,
|
||||
expanded: bool,
|
||||
) -> Cards<'a, Message, crate::Renderer>
|
||||
where
|
||||
Message: 'static + Clone,
|
||||
F: 'a + Fn(bool) -> Message,
|
||||
G: 'a + Fn(usize) -> Message,
|
||||
{
|
||||
Cards::new(
|
||||
id,
|
||||
card_inner_elements,
|
||||
on_clear_all,
|
||||
on_show_more,
|
||||
on_activate,
|
||||
show_more_label,
|
||||
show_less_label,
|
||||
clear_all_label,
|
||||
show_less_icon,
|
||||
expanded,
|
||||
)
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Cards<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_core::text::Renderer,
|
||||
{
|
||||
fn fully_expanded(&self, t: f32) -> bool {
|
||||
self.expanded && self.elements.len() > 1 && self.can_show_more && approx_eq!(f32, t, 1.0)
|
||||
}
|
||||
|
||||
fn fully_unexpanded(&self, t: f32) -> bool {
|
||||
self.elements.len() == 1
|
||||
|| (!self.expanded && (!self.can_show_more || approx_eq!(f32, t, 0.0)))
|
||||
}
|
||||
}
|
||||
|
||||
/// An expandable stack of cards.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Cards<'a, Message, Renderer = crate::Renderer>
|
||||
where
|
||||
Renderer: iced_core::text::Renderer,
|
||||
{
|
||||
id: Id,
|
||||
show_less_button: Element<'a, Message, crate::Theme, Renderer>,
|
||||
clear_all_button: Element<'a, Message, crate::Theme, Renderer>,
|
||||
elements: Vec<Element<'a, Message, crate::Theme, Renderer>>,
|
||||
expanded: bool,
|
||||
can_show_more: bool,
|
||||
width: Length,
|
||||
anim_multiplier: f32,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
impl<'a, Message> Cards<'a, Message, crate::Renderer>
|
||||
where
|
||||
Message: Clone + 'static,
|
||||
{
|
||||
/// Get an expandable stack of cards
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new<F, G>(
|
||||
id: widget::Id,
|
||||
card_inner_elements: Vec<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
||||
on_clear_all: Message,
|
||||
on_show_more: Option<F>,
|
||||
on_activate: Option<G>,
|
||||
show_more_label: &'a str,
|
||||
show_less_label: &'a str,
|
||||
clear_all_label: &'a str,
|
||||
show_less_icon: Option<Handle>,
|
||||
expanded: bool,
|
||||
) -> Self
|
||||
where
|
||||
F: 'a + Fn(bool) -> Message,
|
||||
G: 'a + Fn(usize) -> Message,
|
||||
{
|
||||
let can_show_more = card_inner_elements.len() > 1 && on_show_more.is_some();
|
||||
|
||||
Self {
|
||||
can_show_more,
|
||||
id: Id::unique(),
|
||||
show_less_button: {
|
||||
let mut show_less_children = Vec::with_capacity(3);
|
||||
if let Some(source) = show_less_icon {
|
||||
show_less_children.push(icon::icon(source).size(ICON_SIZE).into());
|
||||
}
|
||||
show_less_children.push(text::body(show_less_label).width(Length::Shrink).into());
|
||||
show_less_children.push(
|
||||
icon::from_name("pan-up-symbolic")
|
||||
.size(ICON_SIZE)
|
||||
.icon()
|
||||
.into(),
|
||||
);
|
||||
|
||||
let button_content = row::with_children(show_less_children)
|
||||
.align_y(iced_core::Alignment::Center)
|
||||
.spacing(TOP_SPACING)
|
||||
.width(Length::Shrink);
|
||||
|
||||
Element::from(
|
||||
button::custom(button_content)
|
||||
.class(crate::theme::Button::Text)
|
||||
.width(Length::Shrink)
|
||||
.on_press_maybe(on_show_more.as_ref().map(|f| f(false)))
|
||||
.padding([PADDING / 2, PADDING]),
|
||||
)
|
||||
},
|
||||
clear_all_button: Element::from(
|
||||
button::custom(text(clear_all_label))
|
||||
.class(crate::theme::Button::Text)
|
||||
.width(Length::Shrink)
|
||||
.on_press(on_clear_all)
|
||||
.padding([PADDING / 2, PADDING]),
|
||||
),
|
||||
elements: card_inner_elements
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, w)| {
|
||||
let custom_content = if i == 0 && !expanded && can_show_more {
|
||||
column::with_capacity(2)
|
||||
.push(w)
|
||||
.push(text::caption(show_more_label))
|
||||
.spacing(VERTICAL_SPACING)
|
||||
.align_x(iced_core::Alignment::Center)
|
||||
.into()
|
||||
} else {
|
||||
w
|
||||
};
|
||||
|
||||
let b = crate::iced::widget::button(custom_content)
|
||||
.class(crate::theme::iced::Button::Card)
|
||||
.padding(PADDING);
|
||||
if i == 0 && !expanded && can_show_more {
|
||||
b.on_press_maybe(on_show_more.as_ref().map(|f| f(true)))
|
||||
} else {
|
||||
b.on_press_maybe(on_activate.as_ref().map(|f| f(i)))
|
||||
}
|
||||
.into()
|
||||
})
|
||||
// we will set the width of the container to shrink, then when laying out the top bar
|
||||
// we will set the fill limit to the max of the shrink top bar width and the max shrink width of the
|
||||
// cards
|
||||
.collect(),
|
||||
width: Length::Shrink,
|
||||
anim_multiplier: 1.0,
|
||||
expanded,
|
||||
duration: Duration::from_millis(200),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the width of the cards stack
|
||||
#[must_use]
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// The default animation time is 100ms, to speed up the toggle
|
||||
/// animation use a value less than 1.0, and to slow down the
|
||||
/// animation use a value greater than 1.0.
|
||||
pub fn anim_multiplier(mut self, multiplier: f32) -> Self {
|
||||
self.anim_multiplier = multiplier;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn duration(mut self, dur: Duration) -> Self {
|
||||
self.duration = dur;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, crate::Theme, Renderer> for Cards<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + iced_core::Renderer + iced_core::text::Renderer,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
[&self.show_less_button, &self.clear_all_button]
|
||||
.iter()
|
||||
.map(|w| Tree::new(w.as_widget()))
|
||||
.chain(self.elements.iter().map(|w| Tree::new(w.as_widget())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
let mut children: Vec<_> = vec![
|
||||
self.show_less_button.as_widget_mut(),
|
||||
self.clear_all_button.as_widget_mut(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
self.elements
|
||||
.iter_mut()
|
||||
.map(iced_core::Element::as_widget_mut),
|
||||
)
|
||||
.collect();
|
||||
|
||||
tree.diff_children(children.as_mut_slice());
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &iced_core::layout::Limits,
|
||||
) -> iced_core::layout::Node {
|
||||
let my_state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let mut children = Vec::with_capacity(1 + self.elements.len());
|
||||
let mut size = Size::new(0.0, 0.0);
|
||||
let tree_children = &mut tree.children;
|
||||
let count = self.elements.len();
|
||||
if self.elements.is_empty() {
|
||||
return Node::with_children(Size::new(1., 1.), children);
|
||||
}
|
||||
let s = anim::smootherstep(my_state.anim.t(self.duration, self.expanded));
|
||||
let fully_expanded: bool = self.fully_expanded(s);
|
||||
let fully_unexpanded: bool = self.fully_unexpanded(s);
|
||||
|
||||
let show_less = &mut self.show_less_button;
|
||||
let clear_all = &mut self.clear_all_button;
|
||||
|
||||
let show_less_node = if self.can_show_more {
|
||||
show_less
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree_children[0], renderer, limits)
|
||||
} else {
|
||||
Node::new(Size::default())
|
||||
};
|
||||
let clear_all_node =
|
||||
clear_all
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree_children[1], renderer, limits);
|
||||
size.width += show_less_node.size().width + clear_all_node.size().width;
|
||||
|
||||
let custom_limits = limits.min_width(size.width);
|
||||
for (c, t) in self.elements.iter_mut().zip(tree_children[2..].iter_mut()) {
|
||||
let card_node = c.as_widget_mut().layout(t, renderer, &custom_limits);
|
||||
size.width = size.width.max(card_node.size().width);
|
||||
}
|
||||
|
||||
if fully_expanded {
|
||||
let show_less = &mut self.show_less_button;
|
||||
let clear_all = &mut self.clear_all_button;
|
||||
|
||||
let show_less_node = if self.can_show_more {
|
||||
show_less
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree_children[0], renderer, limits)
|
||||
} else {
|
||||
Node::new(Size::default())
|
||||
};
|
||||
let clear_all_node = if self.can_show_more {
|
||||
let mut n =
|
||||
clear_all
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree_children[1], renderer, limits);
|
||||
let clear_all_node_size = n.size();
|
||||
n = clear_all_node
|
||||
.translate(Vector::new(size.width - clear_all_node_size.width, 0.0));
|
||||
size.height += show_less_node.size().height.max(n.size().height) + VERTICAL_SPACING;
|
||||
n
|
||||
} else {
|
||||
Node::new(Size::default())
|
||||
};
|
||||
|
||||
children.push(show_less_node);
|
||||
children.push(clear_all_node);
|
||||
}
|
||||
|
||||
let custom_limits = limits
|
||||
.min_width(size.width)
|
||||
.max_width(size.width)
|
||||
.width(Length::Fixed(size.width));
|
||||
|
||||
for (i, (c, t)) in self
|
||||
.elements
|
||||
.iter_mut()
|
||||
.zip(tree_children[2..].iter_mut())
|
||||
.enumerate()
|
||||
{
|
||||
let progress = s * size.height;
|
||||
let card_node = c
|
||||
.as_widget_mut()
|
||||
.layout(t, renderer, &custom_limits)
|
||||
.translate(Vector::new(0.0, progress));
|
||||
|
||||
size.height = size.height.max(progress + card_node.size().height);
|
||||
|
||||
children.push(card_node);
|
||||
|
||||
if fully_unexpanded {
|
||||
let width = children.last().unwrap().bounds().width;
|
||||
|
||||
// push the background card nodes
|
||||
for i in 1..self.elements.len().min(3) {
|
||||
// height must be 16px for 8px padding
|
||||
// but we only want 4px visible
|
||||
|
||||
let margin = f32::from(u8::try_from(i).unwrap()) * BG_CARD_MARGIN_STEP;
|
||||
let node =
|
||||
Node::new(Size::new(width - 2.0 * margin, BG_CARD_BORDER_RADIUS * 2.0))
|
||||
.translate(Vector::new(
|
||||
margin,
|
||||
size.height - BG_CARD_BORDER_RADIUS * 2.0 + BG_CARD_VISIBLE_HEIGHT,
|
||||
));
|
||||
size.height += BG_CARD_VISIBLE_HEIGHT;
|
||||
children.push(node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if i + 1 < count {
|
||||
size.height += VERTICAL_SPACING;
|
||||
}
|
||||
}
|
||||
|
||||
Node::with_children(size, children)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
state: &iced_core::widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &crate::Theme,
|
||||
style: &iced_core::renderer::Style,
|
||||
layout: iced_core::Layout<'_>,
|
||||
cursor: iced_core::mouse::Cursor,
|
||||
viewport: &iced_core::Rectangle,
|
||||
) {
|
||||
let my_state = state.state.downcast_ref::<State>();
|
||||
|
||||
// there are 4 cases for drawing
|
||||
// 1. empty entries list
|
||||
// Nothing to draw
|
||||
// 2. un-expanded
|
||||
// go through the layout, draw the card, the inner card, and the bg cards
|
||||
// 3. expanding / unexpanding
|
||||
// go through the layout. draw each card and its inner card
|
||||
// 4. expanded =>
|
||||
// go through the layout. draw the top bar, and do all of 3
|
||||
// cards may be hovered
|
||||
// any buttons may have a hover state as well
|
||||
if self.elements.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let t = my_state.anim.t(self.duration, self.expanded);
|
||||
let fully_unexpanded = self.fully_unexpanded(t);
|
||||
let fully_expanded = self.fully_expanded(t);
|
||||
|
||||
let mut layout = layout.children();
|
||||
let mut tree_children = state.children.iter();
|
||||
|
||||
if fully_expanded {
|
||||
let show_less = &self.show_less_button;
|
||||
let clear_all = &self.clear_all_button;
|
||||
|
||||
let show_less_layout = layout.next().unwrap();
|
||||
let clear_all_layout = layout.next().unwrap();
|
||||
|
||||
show_less.as_widget().draw(
|
||||
tree_children.next().unwrap(),
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
show_less_layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
|
||||
clear_all.as_widget().draw(
|
||||
tree_children.next().unwrap(),
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
clear_all_layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
} else {
|
||||
_ = tree_children.next();
|
||||
_ = tree_children.next();
|
||||
}
|
||||
|
||||
// Draw first to appear behind
|
||||
if fully_unexpanded {
|
||||
let card_layout = layout.next().unwrap();
|
||||
let appearance = Style::default();
|
||||
let bg_layout = layout.collect::<Vec<_>>();
|
||||
for (i, layout) in (0..2).zip(bg_layout.into_iter()).rev() {
|
||||
renderer.fill_quad(
|
||||
Quad {
|
||||
bounds: layout.bounds(),
|
||||
border: Border {
|
||||
radius: Radius::from([
|
||||
0.0,
|
||||
0.0,
|
||||
BG_CARD_BORDER_RADIUS,
|
||||
BG_CARD_BORDER_RADIUS,
|
||||
]),
|
||||
..Default::default()
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
snap: true,
|
||||
},
|
||||
if i == 0 {
|
||||
appearance.card_1
|
||||
} else {
|
||||
appearance.card_2
|
||||
},
|
||||
);
|
||||
}
|
||||
self.elements[0].as_widget().draw(
|
||||
tree_children.next().unwrap(),
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
card_layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
} else {
|
||||
let layout = layout.collect::<Vec<_>>();
|
||||
// draw in reverse order so later cards appear behind earlier cards
|
||||
for ((inner, layout), c_state) in self
|
||||
.elements
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(layout.into_iter().rev())
|
||||
.zip(tree_children.rev())
|
||||
{
|
||||
inner
|
||||
.as_widget()
|
||||
.draw(c_state, renderer, theme, style, layout, cursor, viewport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
state: &mut Tree,
|
||||
event: &iced_core::Event,
|
||||
layout: iced_core::Layout<'_>,
|
||||
cursor: iced_core::mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn iced_core::Clipboard,
|
||||
shell: &mut iced_core::Shell<'_, Message>,
|
||||
viewport: &iced_core::Rectangle,
|
||||
) {
|
||||
if self.elements.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Event::Window(window::Event::RedrawRequested(_)) = event {
|
||||
let state = state.state.downcast_mut::<State>();
|
||||
|
||||
state.anim.anim_done(self.duration);
|
||||
if state.anim.last_change.is_some() {
|
||||
shell.request_redraw();
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
}
|
||||
|
||||
let my_state = state.state.downcast_ref::<State>();
|
||||
|
||||
let mut layout = layout.children();
|
||||
let mut tree_children = state.children.iter_mut();
|
||||
let t = my_state.anim.t(self.duration, self.expanded);
|
||||
let fully_expanded = self.fully_expanded(t);
|
||||
let fully_unexpanded = self.fully_unexpanded(t);
|
||||
let show_less_state = tree_children.next();
|
||||
let clear_all_state = tree_children.next();
|
||||
|
||||
if fully_expanded {
|
||||
let c_layout = layout.next().unwrap();
|
||||
let state = show_less_state.unwrap();
|
||||
self.show_less_button.as_widget_mut().update(
|
||||
state, event, c_layout, cursor, renderer, clipboard, shell, viewport,
|
||||
);
|
||||
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
let c_layout = layout.next().unwrap();
|
||||
let state = clear_all_state.unwrap();
|
||||
self.clear_all_button.as_widget_mut().update(
|
||||
state, &event, c_layout, cursor, renderer, clipboard, shell, viewport,
|
||||
);
|
||||
}
|
||||
|
||||
if shell.is_event_captured() {
|
||||
return;
|
||||
}
|
||||
|
||||
for ((inner, layout), c_state) in self.elements.iter_mut().zip(layout).zip(tree_children) {
|
||||
inner.as_widget_mut().update(
|
||||
c_state, &event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||
);
|
||||
if shell.is_event_captured() || fully_unexpanded {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(self.width, Length::Shrink)
|
||||
}
|
||||
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<Id> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: Id) {
|
||||
self.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> From<Cards<'a, Message>> for Element<'a, Message, crate::Theme, crate::Renderer>
|
||||
where
|
||||
Message: Clone + 'a,
|
||||
{
|
||||
fn from(cards: Cards<'a, Message>) -> Self {
|
||||
Self::new(cards)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
anim: anim::State,
|
||||
}
|
||||
|
|
@ -249,7 +249,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(self.content.as_widget_mut());
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
state.menu_bar_state.inner.with_data_mut(|inner| {
|
||||
menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree);
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(self.container.as_widget_mut());
|
||||
tree.diff_children(std::slice::from_mut(&mut self.container));
|
||||
}
|
||||
|
||||
fn state(&self) -> iced_core::widget::tree::State {
|
||||
|
|
@ -337,7 +337,7 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let s = self.container.as_widget_mut().update(
|
||||
self.container.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
|
|
|
|||
|
|
@ -131,21 +131,25 @@ impl<
|
|||
);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_start(mut self, on_start: Option<Message>) -> Self {
|
||||
self.on_start = on_start;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_cancel(mut self, on_cancelled: Option<Message>) -> Self {
|
||||
self.on_cancelled = on_cancelled;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn on_finish(mut self, on_finish: Option<Message>) -> Self {
|
||||
self.on_finish = on_finish;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn window(mut self, window: window::Id) -> Self {
|
||||
self.window = Some(window);
|
||||
self
|
||||
|
|
@ -164,7 +168,7 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(self.container.as_widget_mut());
|
||||
tree.diff_children(std::slice::from_mut(&mut self.container));
|
||||
}
|
||||
|
||||
fn state(&self) -> iced_core::widget::tree::State {
|
||||
|
|
@ -197,19 +201,15 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.container.as_widget_mut().operate(
|
||||
tree,
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
});
|
||||
operation.custom(
|
||||
Some(&self.id),
|
||||
layout.bounds(),
|
||||
(&mut tree.state) as &mut dyn Any,
|
||||
);
|
||||
|
||||
self.container
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
|
@ -223,9 +223,9 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let ret = self.container.as_widget_mut().update(
|
||||
self.container.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
&event,
|
||||
event,
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
|
|
@ -241,12 +241,11 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
mouse::Event::ButtonPressed(mouse::Button::Left) => {
|
||||
if let Some(position) = cursor.position() {
|
||||
if !state.hovered {
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
|
||||
state.left_pressed_position = Some(position);
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left)
|
||||
|
|
@ -254,7 +253,6 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
{
|
||||
state.left_pressed_position = None;
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
mouse::Event::CursorMoved { .. } => {
|
||||
if let Some(position) = cursor.position() {
|
||||
|
|
@ -262,7 +260,7 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
// We ignore motion if we do not possess drag content by now.
|
||||
if self.drag_content.is_none() {
|
||||
state.left_pressed_position = None;
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
if let Some(left_pressed_position) = state.left_pressed_position {
|
||||
if position.distance(left_pressed_position) > self.drag_threshold {
|
||||
|
|
@ -281,16 +279,15 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
if !cursor.is_over(layout.bounds()) {
|
||||
state.hovered = false;
|
||||
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
} else if cursor.is_over(layout.bounds()) {
|
||||
state.hovered = true;
|
||||
}
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => return ret,
|
||||
_ => (),
|
||||
},
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Cancelled)) => {
|
||||
if state.is_dragging {
|
||||
|
|
@ -301,7 +298,6 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Event::Dnd(DndEvent::Source(SourceEvent::Finished)) => {
|
||||
if state.is_dragging {
|
||||
|
|
@ -312,11 +308,9 @@ impl<Message: Clone + 'static, D: iced::clipboard::mime::AsMimeTypes + std::mark
|
|||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
_ => return ret,
|
||||
_ => (),
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::cosmic_theme::{Density, Spacing};
|
|||
use crate::{Element, theme, widget};
|
||||
use apply::Apply;
|
||||
use derive_setters::Setters;
|
||||
use iced::Length;
|
||||
use iced::{Length, mouse};
|
||||
use iced_core::{Vector, Widget, widget::tree};
|
||||
use std::{borrow::Cow, cmp};
|
||||
|
||||
|
|
@ -206,6 +206,7 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
) {
|
||||
let child_state = &mut state.children[0];
|
||||
let child_layout = layout.children().next().unwrap();
|
||||
|
||||
self.header_bar_inner.as_widget_mut().update(
|
||||
child_state,
|
||||
event,
|
||||
|
|
@ -215,7 +216,7 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
@ -435,6 +436,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
if let Some(message) = self.on_maximize.clone() {
|
||||
widget = widget.on_release(message);
|
||||
}
|
||||
|
||||
if let Some(message) = self.on_double_click.clone() {
|
||||
widget = widget.on_double_press(message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.content);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
|
|
@ -88,7 +88,7 @@ where
|
|||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
tree,
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
|
|
@ -124,7 +124,7 @@ where
|
|||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ impl<'a, Message: 'static> ListColumn<'a, Message> {
|
|||
crate::widget::column::with_children(self.children)
|
||||
.spacing(self.spacing)
|
||||
.padding(self.padding)
|
||||
.width(iced::Length::Fill)
|
||||
.apply(container)
|
||||
.padding([self.spacing, 0])
|
||||
.class(self.style)
|
||||
|
|
|
|||
|
|
@ -580,7 +580,7 @@ where
|
|||
&mut self.menu_roots,
|
||||
view_cursor,
|
||||
tree,
|
||||
&event,
|
||||
event,
|
||||
layout,
|
||||
renderer,
|
||||
clipboard,
|
||||
|
|
@ -609,6 +609,13 @@ where
|
|||
});
|
||||
|
||||
match event {
|
||||
Mouse(mouse::Event::ButtonPressed(Left))
|
||||
| Touch(touch::Event::FingerPressed { .. })
|
||||
if view_cursor.is_over(layout.bounds()) =>
|
||||
{
|
||||
// TODO should we track that it has been pressed?
|
||||
shell.capture_event();
|
||||
}
|
||||
Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => {
|
||||
let create_popup = my_state.inner.with_data_mut(|state| {
|
||||
let mut create_popup = false;
|
||||
|
|
@ -627,6 +634,7 @@ where
|
|||
))]
|
||||
{
|
||||
let surface_action = self.on_surface_action.as_ref().unwrap();
|
||||
shell.capture_event();
|
||||
|
||||
shell.publish(surface_action(crate::surface::action::destroy_popup(
|
||||
_id,
|
||||
|
|
@ -640,6 +648,7 @@ where
|
|||
if !create_popup {
|
||||
return;
|
||||
}
|
||||
shell.capture_event();
|
||||
#[cfg(all(
|
||||
feature = "multi-window",
|
||||
feature = "wayland",
|
||||
|
|
@ -653,6 +662,7 @@ where
|
|||
Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered)
|
||||
if open && view_cursor.is_over(layout.bounds()) =>
|
||||
{
|
||||
shell.capture_event();
|
||||
#[cfg(all(
|
||||
feature = "multi-window",
|
||||
feature = "wayland",
|
||||
|
|
|
|||
|
|
@ -1008,7 +1008,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
|
|||
&mut menu,
|
||||
renderer,
|
||||
shell,
|
||||
cursor.position().unwrap(),
|
||||
cursor.position().unwrap_or_default(),
|
||||
layout.bounds().size(),
|
||||
Vector::new(0., 0.),
|
||||
layout.bounds(),
|
||||
|
|
@ -1181,7 +1181,6 @@ pub(crate) fn init_root_menu<Message: Clone>(
|
|||
menu_bounds,
|
||||
};
|
||||
state.menu_states.push(ms);
|
||||
|
||||
// Hack to ensure menu opens properly
|
||||
shell.invalidate_layout();
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ pub use color_picker::{ColorPicker, ColorPickerModel};
|
|||
#[doc(inline)]
|
||||
pub use iced::widget::qr_code;
|
||||
|
||||
mod cards;
|
||||
#[doc(inline)]
|
||||
pub use cards::cards;
|
||||
|
||||
pub mod context_drawer;
|
||||
#[doc(inline)]
|
||||
pub use context_drawer::{ContextDrawer, context_drawer};
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let tree = content_tree_mut(tree);
|
||||
let tree = &mut tree.children[0];
|
||||
self.content.as_widget_mut().layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
|
|
@ -138,19 +138,9 @@ where
|
|||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
tree,
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
.unwrap()
|
||||
.with_virtual_offset(layout.virtual_offset()),
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
});
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.operate(content_tree_mut(tree), layout, renderer, operation);
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
|
@ -183,7 +173,7 @@ where
|
|||
}
|
||||
|
||||
self.content.as_widget_mut().update(
|
||||
content_tree_mut(tree),
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -265,7 +255,6 @@ where
|
|||
overlay_position.y = overlay_position.y.round();
|
||||
translation.x += overlay_position.x;
|
||||
translation.y += overlay_position.y;
|
||||
|
||||
Some(overlay::Element::new(Box::new(Overlay {
|
||||
tree: &mut tree.children[1],
|
||||
content: popup,
|
||||
|
|
@ -275,7 +264,7 @@ where
|
|||
})))
|
||||
} else {
|
||||
self.content.as_widget_mut().overlay(
|
||||
content_tree_mut(tree),
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.label);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.label));
|
||||
}
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ where
|
|||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.content);
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
|
|
@ -131,7 +131,7 @@ where
|
|||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
tree,
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
|
|
|
|||
|
|
@ -2047,6 +2047,9 @@ where
|
|||
bounds.y = center_y;
|
||||
|
||||
if self.model.text(key).is_some_and(|text| !text.is_empty()) {
|
||||
// FIXME why has this behavior changed? Does the center alignment not work with infinite bounds now?
|
||||
bounds.y -= state.paragraphs[key].min_height() / 2.;
|
||||
|
||||
// Draw the text for this segmented button or tab.
|
||||
renderer.fill_paragraph(
|
||||
state.paragraphs[key].raw(),
|
||||
|
|
@ -2055,7 +2058,9 @@ where
|
|||
Rectangle {
|
||||
x: bounds.x,
|
||||
width: bounds.width,
|
||||
..original_bounds
|
||||
height: original_bounds.height,
|
||||
y: bounds.y,
|
||||
// ..original_bounds,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,418 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
//! Show toggle controls using togglers.
|
||||
|
||||
use iced::{Length, widget};
|
||||
use iced_core::text;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub fn toggler<'a, Message, Theme: iced_widget::toggler::Catalog, Renderer>(
|
||||
is_checked: bool,
|
||||
) -> widget::Toggler<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer + text::Renderer,
|
||||
{
|
||||
widget::Toggler::new(is_checked)
|
||||
.size(24)
|
||||
.spacing(0)
|
||||
.width(Length::Shrink)
|
||||
use crate::{Element, anim, iced_core::Border, iced_widget::toggler::Status};
|
||||
use iced_core::{
|
||||
Clipboard, Event, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, alignment, event,
|
||||
layout, mouse,
|
||||
renderer::{self, Renderer},
|
||||
text,
|
||||
widget::{self, Tree, tree},
|
||||
window,
|
||||
};
|
||||
use iced_widget::Id;
|
||||
|
||||
pub use crate::iced_widget::toggler::{Catalog, Style};
|
||||
|
||||
pub fn toggler<'a, Message>(is_checked: bool) -> Toggler<'a, Message> {
|
||||
Toggler::new(is_checked)
|
||||
}
|
||||
/// A toggler widget.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Toggler<'a, Message> {
|
||||
id: Id,
|
||||
is_toggled: bool,
|
||||
on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>,
|
||||
label: Option<String>,
|
||||
width: Length,
|
||||
size: f32,
|
||||
text_size: Option<f32>,
|
||||
text_line_height: text::LineHeight,
|
||||
text_alignment: text::Alignment,
|
||||
text_shaping: text::Shaping,
|
||||
spacing: f32,
|
||||
font: Option<crate::font::Font>,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
impl<'a, Message> Toggler<'a, Message> {
|
||||
/// The default size of a [`Toggler`].
|
||||
pub const DEFAULT_SIZE: f32 = 24.0;
|
||||
|
||||
/// Creates a new [`Toggler`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * a boolean describing whether the [`Toggler`] is checked or not
|
||||
/// * An optional label for the [`Toggler`]
|
||||
/// * a function that will be called when the [`Toggler`] is toggled. It
|
||||
/// will receive the new state of the [`Toggler`] and must produce a
|
||||
/// `Message`.
|
||||
pub fn new(is_toggled: bool) -> Self {
|
||||
Toggler {
|
||||
id: Id::unique(),
|
||||
is_toggled,
|
||||
on_toggle: None,
|
||||
label: None,
|
||||
width: Length::Fill,
|
||||
size: Self::DEFAULT_SIZE,
|
||||
text_size: None,
|
||||
text_line_height: text::LineHeight::default(),
|
||||
text_alignment: text::Alignment::Left,
|
||||
text_shaping: text::Shaping::Advanced,
|
||||
spacing: 0.0,
|
||||
font: None,
|
||||
duration: Duration::from_millis(200),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the size of the [`Toggler`].
|
||||
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
|
||||
self.size = size.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Toggler`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the text size o the [`Toggler`].
|
||||
pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self {
|
||||
self.text_size = Some(text_size.into().0);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the text [`LineHeight`] of the [`Toggler`].
|
||||
pub fn text_line_height(mut self, line_height: impl Into<text::LineHeight>) -> Self {
|
||||
self.text_line_height = line_height.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of the text of the [`Toggler`]
|
||||
pub fn text_alignment(mut self, alignment: text::Alignment) -> Self {
|
||||
self.text_alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`text::Shaping`] strategy of the [`Toggler`].
|
||||
pub fn text_shaping(mut self, shaping: text::Shaping) -> Self {
|
||||
self.text_shaping = shaping;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the spacing between the [`Toggler`] and the text.
|
||||
pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self {
|
||||
self.spacing = spacing.into().0;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Font`] of the text of the [`Toggler`]
|
||||
///
|
||||
/// [`Font`]: cosmic::iced::text::Renderer::Font
|
||||
pub fn font(mut self, font: impl Into<crate::font::Font>) -> Self {
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn duration(mut self, dur: Duration) -> Self {
|
||||
self.duration = dur;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_toggle(mut self, on_toggle: impl Fn(bool) -> Message + 'a) -> Self {
|
||||
self.on_toggle = Some(Box::new(on_toggle));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the label of the [`Button`].
|
||||
pub fn label(mut self, label: impl Into<Option<String>>) -> Self {
|
||||
self.label = label.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> Widget<Message, crate::Theme, crate::Renderer> for Toggler<'a, Message> {
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(self.width, Length::Shrink)
|
||||
}
|
||||
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<Id> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: Id) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits.width(self.width);
|
||||
|
||||
let res = next_to_each_other(
|
||||
&limits,
|
||||
self.spacing,
|
||||
|limits| {
|
||||
if let Some(label) = self.label.as_deref() {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let node = iced_core::widget::text::layout(
|
||||
&mut state.text,
|
||||
renderer,
|
||||
limits,
|
||||
label,
|
||||
widget::text::Format {
|
||||
width: self.width,
|
||||
height: Length::Shrink,
|
||||
line_height: self.text_line_height,
|
||||
size: self.text_size.map(iced::Pixels),
|
||||
font: self.font,
|
||||
align_x: self.text_alignment,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: self.text_shaping,
|
||||
wrapping: crate::iced_core::text::Wrapping::default(),
|
||||
},
|
||||
);
|
||||
match self.width {
|
||||
Length::Fill => {
|
||||
let size = node.size();
|
||||
layout::Node::with_children(
|
||||
Size::new(limits.width(Length::Fill).max().width, size.height),
|
||||
vec![node],
|
||||
)
|
||||
}
|
||||
_ => node,
|
||||
}
|
||||
} else {
|
||||
layout::Node::new(iced_core::Size::ZERO)
|
||||
}
|
||||
},
|
||||
|_| layout::Node::new(Size::new(48., 24.)),
|
||||
);
|
||||
res
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_renderer: &crate::Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let Some(on_toggle) = self.on_toggle.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||
let mouse_over = cursor_position.is_over(layout.bounds());
|
||||
|
||||
if mouse_over {
|
||||
shell.publish((on_toggle)(!self.is_toggled));
|
||||
state.anim.changed(self.duration);
|
||||
shell.capture_event();
|
||||
}
|
||||
}
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
state.anim.anim_done(self.duration);
|
||||
if state.anim.last_change.is_some() {
|
||||
shell.request_redraw();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if cursor_position.is_over(layout.bounds()) {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut crate::Renderer,
|
||||
theme: &crate::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
|
||||
let mut children = layout.children();
|
||||
let label_layout = children.next().unwrap();
|
||||
|
||||
if let Some(_label) = &self.label {
|
||||
let state: &State = tree.state.downcast_ref();
|
||||
iced_widget::text::draw(
|
||||
renderer,
|
||||
style,
|
||||
label_layout.bounds(),
|
||||
state.text.raw(),
|
||||
iced_widget::text::Style::default(),
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
let toggler_layout = children.next().unwrap();
|
||||
let bounds = toggler_layout.bounds();
|
||||
|
||||
let is_mouse_over = cursor_position.is_over(bounds);
|
||||
|
||||
// let style = blend_appearances(
|
||||
// theme.style(
|
||||
// &(),
|
||||
// if is_mouse_over {
|
||||
// Status::Hovered { is_toggled: false }
|
||||
// } else {
|
||||
// Status::Active { is_toggled: false }
|
||||
// },
|
||||
// ),
|
||||
// theme.style(
|
||||
// &(),
|
||||
// if is_mouse_over {
|
||||
// Status::Hovered { is_toggled: true }
|
||||
// } else {
|
||||
// Status::Active { is_toggled: true }
|
||||
// },
|
||||
// ),
|
||||
// percent,
|
||||
// );
|
||||
|
||||
let style = theme.style(
|
||||
&(),
|
||||
if is_mouse_over {
|
||||
Status::Hovered {
|
||||
is_toggled: self.is_toggled,
|
||||
}
|
||||
} else {
|
||||
Status::Active {
|
||||
is_toggled: self.is_toggled,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let space = style.handle_margin;
|
||||
|
||||
let toggler_background_bounds = Rectangle {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: toggler_background_bounds,
|
||||
border: Border {
|
||||
radius: style.border_radius,
|
||||
..Default::default()
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
let mut t = state.anim.t(self.duration, self.is_toggled);
|
||||
|
||||
let toggler_foreground_bounds = Rectangle {
|
||||
x: bounds.x
|
||||
+ anim::slerp(
|
||||
space,
|
||||
bounds.width - space - (bounds.height - (2.0 * space)),
|
||||
t,
|
||||
),
|
||||
|
||||
y: bounds.y + space,
|
||||
width: bounds.height - (2.0 * space),
|
||||
height: bounds.height - (2.0 * space),
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: toggler_foreground_bounds,
|
||||
border: Border {
|
||||
radius: style.handle_radius,
|
||||
..Default::default()
|
||||
},
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
style.foreground,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> From<Toggler<'a, Message>> for Element<'a, Message> {
|
||||
fn from(toggler: Toggler<'a, Message>) -> Element<'a, Message> {
|
||||
Element::new(toggler)
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Node`] with two children nodes one right next to each other.
|
||||
pub fn next_to_each_other(
|
||||
limits: &iced::Limits,
|
||||
spacing: f32,
|
||||
left: impl FnOnce(&iced::Limits) -> iced_core::layout::Node,
|
||||
right: impl FnOnce(&iced::Limits) -> iced_core::layout::Node,
|
||||
) -> iced_core::layout::Node {
|
||||
let mut right_node = right(limits);
|
||||
let right_size = right_node.size();
|
||||
|
||||
let left_limits = limits.shrink(Size::new(right_size.width + spacing, 0.0));
|
||||
let mut left_node = left(&left_limits);
|
||||
let left_size = left_node.size();
|
||||
|
||||
let (left_y, right_y) = if left_size.height > right_size.height {
|
||||
(0.0, (left_size.height - right_size.height) / 2.0)
|
||||
} else {
|
||||
((right_size.height - left_size.height) / 2.0, 0.0)
|
||||
};
|
||||
|
||||
left_node = left_node.move_to(iced::Point::new(0.0, left_y));
|
||||
right_node = right_node.move_to(iced::Point::new(left_size.width + spacing, right_y));
|
||||
|
||||
iced_core::layout::Node::with_children(
|
||||
Size::new(
|
||||
left_size.width + spacing + right_size.width,
|
||||
left_size.height.max(right_size.height),
|
||||
),
|
||||
vec![left_node, right_node],
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
text: widget::text::State<<crate::Renderer as iced_core::text::Renderer>::Paragraph>,
|
||||
anim: anim::State,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ impl<'a, Message: 'static + Clone, TopLevelMessage: 'static + Clone>
|
|||
operation.container(Some(&self.id), layout.bounds());
|
||||
operation.traverse(&mut |operation| {
|
||||
self.content.as_widget_mut().operate(
|
||||
tree,
|
||||
&mut tree.children[0],
|
||||
layout
|
||||
.children()
|
||||
.next()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue