cosmic-applets/cosmic-applet-minimize/src/window_image.rs
Ashley Wulber 836c0e378b applet overlap
chore: mpris dep

fix: dock buttons

improv: battery and audio improvements

feat: overlapping padding

fix: input source text button

fix: apply panel spacing to app tray

chore: update libcosmic

fix: spacing and padding

fix(minimize): hide when empty
2025-11-11 16:49:48 -05:00

290 lines
9.7 KiB
Rust

// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
Element,
desktop::{IconSourceExt, fde},
iced::Limits,
iced_core::{Border, Layout, Length, Size, Vector, layout, overlay, widget::Tree},
theme::{Button, Container},
widget::{Image, Widget, button, container, image::Handle},
};
use crate::wayland_subscription::WaylandImage;
pub struct WindowImage<'a, Msg> {
image_button: Element<'a, Msg>,
icon: Element<'a, Msg>,
}
impl<Msg> WindowImage<'_, Msg>
where
Msg: 'static + Clone,
{
pub fn new(
img: Option<WaylandImage>,
icon: &fde::IconSource,
size: f32,
on_press: Msg,
padding: (u16, u16),
) -> Self {
let border = 1.0;
Self {
image_button: button::custom(
container(
container(if let Some(img) = img {
let max_dim = img.width.max(img.height).max(1);
let ratio = max_dim as f32 / (size - border * 2.0).max(1.0);
let adjusted_width = img.width as f32 / ratio;
let adjusted_height = img.height as f32 / ratio;
Element::from(
Image::new(Handle::from_rgba(img.width, img.height, img.img))
.width(Length::Fixed(adjusted_width))
.height(Length::Fixed(adjusted_height))
.content_fit(cosmic::iced_core::ContentFit::Contain),
)
} else {
Element::from(
icon.as_cosmic_icon()
.width(Length::Fixed((size - border * 2.0).max(0.)))
.height(Length::Fixed((size - border * 2.0).max(0.))),
)
})
.class(Container::Custom(Box::new(move |theme| container::Style {
border: Border {
color: theme.cosmic().bg_divider().into(),
width: border,
radius: 0.0.into(),
},
..Default::default()
})))
.padding(border as u16)
.height(Length::Shrink)
.width(Length::Shrink),
)
.center_x(Length::Fixed(size + padding.0 as f32 * 2.0))
.center_y(Length::Fixed(size + padding.1 as f32 * 2.0))
.padding([padding.0 as f32, padding.1 as f32]),
)
.on_press(on_press)
.width(Length::Shrink)
.height(Length::Shrink)
.class(Button::AppletIcon)
.padding(0)
.into(),
icon: icon
.as_cosmic_icon()
.width(Length::Fixed(size / 3.0))
.height(Length::Fixed(size / 3.0))
.into(),
}
}
}
impl<Msg> Widget<Msg, cosmic::Theme, cosmic::Renderer> for WindowImage<'_, Msg> {
fn children(&self) -> Vec<cosmic::iced_core::widget::Tree> {
vec![Tree::new(&self.image_button), Tree::new(&self.icon)]
}
fn diff(&mut self, tree: &mut cosmic::iced_core::widget::Tree) {
tree.diff_children(&mut [&mut self.image_button, &mut self.icon]);
}
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &cosmic::Renderer,
translation: Vector,
) -> Option<cosmic::iced_core::overlay::Element<'b, Msg, cosmic::Theme, cosmic::Renderer>> {
let children = [&mut self.image_button, &mut self.icon]
.into_iter()
.zip(&mut state.children)
.zip(layout.children())
.filter_map(|((child, state), layout)| {
child
.as_widget_mut()
.overlay(state, layout, renderer, translation)
})
.collect::<Vec<_>>();
(!children.is_empty()).then(|| overlay::Group::with_children(children).overlay())
}
fn size(&self) -> Size<Length> {
Size::new(Length::Shrink, Length::Shrink)
}
fn layout(
&self,
tree: &mut cosmic::iced_core::widget::Tree,
renderer: &cosmic::Renderer,
limits: &cosmic::iced_core::layout::Limits,
) -> cosmic::iced_core::layout::Node {
let children = &mut tree.children;
let button = &mut children[0];
let button_node = self
.image_button
.as_widget()
.layout(button, renderer, limits);
let img_node = &button_node.children()[0].children()[0];
let button_bounds = img_node.size();
let icon_width = button_bounds.width.max(button_bounds.height) / 3.0;
let icon_height = button_bounds.height.max(button_bounds.width) / 3.0;
let icon = &mut children[1];
let icon_node = self
.icon
.as_widget()
.layout(
icon,
renderer,
&Limits::NONE.width(icon_width).height(icon_height),
)
.translate(Vector::new(
img_node.bounds().x + 2. * button_bounds.width / 3.0,
img_node.bounds().y + 2. * button_bounds.height / 3.0,
));
layout::Node::with_children(
limits.resolve(Length::Shrink, Length::Shrink, button_node.size()),
vec![button_node, icon_node],
)
}
fn draw(
&self,
tree: &cosmic::iced_core::widget::Tree,
renderer: &mut cosmic::Renderer,
theme: &cosmic::Theme,
style: &cosmic::iced_core::renderer::Style,
layout: cosmic::iced_core::Layout<'_>,
cursor: cosmic::iced_core::mouse::Cursor,
viewport: &cosmic::iced_core::Rectangle,
) {
let children = &[&self.image_button, &self.icon];
// draw children in order
for (i, (layout, child)) in layout.children().zip(children).enumerate() {
let tree = &tree.children[i];
child
.as_widget()
.draw(tree, renderer, theme, style, layout, cursor, viewport);
}
}
fn size_hint(&self) -> Size<Length> {
self.size()
}
fn tag(&self) -> cosmic::iced_core::widget::tree::Tag {
cosmic::iced_core::widget::tree::Tag::stateless()
}
fn state(&self) -> cosmic::iced_core::widget::tree::State {
cosmic::iced_core::widget::tree::State::None
}
fn operate(
&self,
tree: &mut cosmic::iced_core::widget::Tree,
layout: cosmic::iced_core::Layout<'_>,
renderer: &cosmic::Renderer,
operation: &mut dyn cosmic::widget::Operation<()>,
) {
let layout = layout.children().collect::<Vec<_>>();
let children = [&self.image_button, &self.icon];
for (i, (layout, child)) in layout
.into_iter()
.zip(children.into_iter())
.enumerate()
.rev()
{
let tree = &mut tree.children[i];
child.as_widget().operate(tree, layout, renderer, operation);
}
}
fn on_event(
&mut self,
state: &mut cosmic::iced_core::widget::Tree,
event: cosmic::iced_core::Event,
layout: cosmic::iced_core::Layout<'_>,
cursor: cosmic::iced_core::mouse::Cursor,
renderer: &cosmic::Renderer,
clipboard: &mut dyn cosmic::iced_core::Clipboard,
shell: &mut cosmic::iced_core::Shell<'_, Msg>,
viewport: &cosmic::iced_core::Rectangle,
) -> cosmic::iced_core::event::Status {
let children = [&mut self.image_button, &mut self.icon];
let layout = layout.children().collect::<Vec<_>>();
// draw children in order
let mut status = cosmic::iced_core::event::Status::Ignored;
for (i, (layout, child)) in layout
.into_iter()
.zip(children.into_iter())
.enumerate()
.rev()
{
let tree = &mut state.children[i];
status = child.as_widget_mut().on_event(
tree,
event.clone(),
layout,
cursor,
renderer,
clipboard,
shell,
viewport,
);
if matches!(status, cosmic::iced_core::event::Status::Captured) {
return status;
}
}
status
}
fn mouse_interaction(
&self,
state: &cosmic::iced_core::widget::Tree,
layout: cosmic::iced_core::Layout<'_>,
cursor: cosmic::iced_core::mouse::Cursor,
viewport: &cosmic::iced_core::Rectangle,
renderer: &cosmic::Renderer,
) -> cosmic::iced_core::mouse::Interaction {
let children = [&self.image_button, &self.icon];
let layout = layout.children().collect::<Vec<_>>();
for (i, (layout, child)) in layout
.into_iter()
.zip(children.into_iter())
.enumerate()
.rev()
{
let tree = &state.children[i];
let interaction = child
.as_widget()
.mouse_interaction(tree, layout, cursor, viewport, renderer);
if cursor.is_over(layout.bounds()) {
return interaction;
}
}
cosmic::iced_core::mouse::Interaction::Idle
}
fn id(&self) -> Option<cosmic::widget::Id> {
None
}
fn set_id(&mut self, _id: cosmic::widget::Id) {}
}
impl<'a, Message> From<WindowImage<'a, Message>> for cosmic::Element<'a, Message>
where
Message: 'static + Clone,
{
fn from(w: WindowImage<'a, Message>) -> cosmic::Element<'a, Message> {
Element::new(w)
}
}