chore: various fixes and some cleanup

This commit is contained in:
Ashley Wulber 2026-02-19 18:15:22 -05:00
parent e10459fb37
commit e8d53b14ea
27 changed files with 1181 additions and 107 deletions

View file

@ -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",
"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]

View file

@ -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"]

View file

@ -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())
}
}

View file

@ -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",

View file

@ -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
View 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 }
}
}

View file

@ -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| {

View file

@ -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,

View file

@ -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")]

View file

@ -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(

View file

@ -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
View 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,
}

View file

@ -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);

View file

@ -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,

View file

@ -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(

View file

@ -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);
}

View file

@ -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(

View file

@ -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)

View file

@ -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",

View file

@ -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();

View file

@ -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};

View file

@ -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,

View file

@ -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 {

View file

@ -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()

View file

@ -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,
},
);
}

View file

@ -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,
}

View file

@ -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()