diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 8f621c19..c9eb760b 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -468,6 +468,7 @@ impl Application for Window { }; let mut header = header_bar() + .window_id(window::Id::MAIN) .title("COSMIC Design System - Iced") .on_close(Message::Close) .on_drag(Message::Drag) diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index b9b29c84..dd47212b 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -5,7 +5,7 @@ use cosmic::{ iced::{self, event, window}, iced_core::{id, Alignment, Length, Point}, iced_widget::{column, container, scrollable, text, text_input}, - widget::{button, cosmic_container}, + widget::{button, header_bar}, ApplicationExt, Command, }; @@ -97,6 +97,7 @@ impl cosmic::Application for MultiWindow { let (id, spawn_window) = window::spawn(window::Settings { position: Default::default(), exit_on_close_request: count % 2 == 0, + decorations: false, ..Default::default() }); @@ -140,13 +141,18 @@ impl cosmic::Application for MultiWindow { .align_items(Alignment::Center), ); - container(container(content).width(200).center_x()) + let window_content = container(container(content).width(200).center_x()) .style(cosmic::style::Container::Background) .width(Length::Fill) .height(Length::Fill) .center_x() - .center_y() - .into() + .center_y(); + + if id == window::Id::MAIN { + window_content.into() + } else { + column![header_bar().window_id(id), window_content].into() + } } fn view(&self) -> cosmic::prelude::Element { diff --git a/src/app/mod.rs b/src/app/mod.rs index 21dcf29a..9eee5274 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -647,6 +647,7 @@ impl ApplicationExt for App { .push_maybe(if core.window.show_headerbar { Some({ let mut header = crate::widget::header_bar() + .window_id(window::Id::MAIN) .title(&core.window.header_title) .on_drag(Message::Cosmic(cosmic::Message::Drag)) .on_close(Message::Cosmic(cosmic::Message::Close)); diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 603f5a73..34e2fef5 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -385,18 +385,12 @@ impl container::StyleSheet for Theme { } Container::HeaderBar => { let palette = self.cosmic(); - let mut header_top = palette.background.base; - let header_bottom = palette.background.base; - header_top.alpha = 0.8; + let header_top = palette.background.base; container::Appearance { icon_color: Some(Color::from(palette.accent.base)), text_color: Some(Color::from(palette.background.on)), - background: Some(iced::Background::Gradient(iced_core::Gradient::Linear( - Linear::new(Radians(PI)) - .add_stop(0.0, header_top.into()) - .add_stop(1.0, header_bottom.into()), - ))), + background: Some(iced::Background::Color(header_top.into())), border_radius: BorderRadius::from([ palette.corner_radii.radius_xs[0], palette.corner_radii.radius_xs[3], diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index ff0e9925..7de4de28 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -4,8 +4,9 @@ use crate::{ext::CollectionWidget, widget, Element}; use apply::Apply; use derive_setters::Setters; -use iced::Length; -use std::borrow::Cow; +use iced::{window, Length}; +use iced_core::{renderer::Quad, widget::tree, Background, Color, Renderer, Widget}; +use std::{borrow::Cow, process::Child}; #[must_use] pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { @@ -18,6 +19,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { start: Vec::new(), center: Vec::new(), end: Vec::new(), + window_id: None, } } @@ -43,6 +45,10 @@ pub struct HeaderBar<'a, Message> { #[setters(strip_option)] on_minimize: Option, + /// The window id for the headerbar. + #[setters(strip_option)] + window_id: Option, + /// Elements packed at the start of the headerbar. #[setters(skip)] start: Vec>, @@ -84,6 +90,194 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { self.end.push(widget.into()); self } + + /// Build the widget + #[must_use] + pub fn build(self) -> HeaderBarWidget<'a, Message> { + HeaderBarWidget { + window_id: self.window_id, + header_bar_inner: self.into_element(), + } + } +} + +pub struct HeaderBarWidget<'a, Message> { + header_bar_inner: Element<'a, Message>, + window_id: Option, +} + +impl<'a, Message: Clone + 'static> Widget + for HeaderBarWidget<'a, Message> +{ + fn children(&self) -> Vec { + vec![tree::Tree::new(&self.header_bar_inner)] + } + + fn width(&self) -> Length { + self.header_bar_inner.width() + } + + fn height(&self) -> Length { + self.header_bar_inner.height() + } + + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn diff(&mut self, tree: &mut tree::Tree) { + tree.diff_children(&mut [&mut self.header_bar_inner]); + let prev = tree.state.downcast_mut::(); + if prev.window_id != self.window_id { + *prev = MyState::new(self.window_id); + } + } + + fn state(&self) -> tree::State { + let state = MyState::new(self.window_id); + tree::State::new(state) + } + + fn layout( + &self, + tree: &mut tree::Tree, + renderer: &crate::Renderer, + limits: &iced_core::layout::Limits, + ) -> iced_core::layout::Node { + let child_tree = &mut tree.children[0]; + let child = self + .header_bar_inner + .as_widget() + .layout(child_tree, renderer, limits); + iced_core::layout::Node::with_children(child.size(), vec![child]) + } + + fn draw( + &self, + tree: &tree::Tree, + renderer: &mut crate::Renderer, + theme: &::Theme, + style: &iced_core::renderer::Style, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, + viewport: &iced_core::Rectangle, + ) { + let layout_children = layout.children().next().unwrap(); + let state_children = &tree.children[0]; + self.header_bar_inner.as_widget().draw( + state_children, + renderer, + theme, + style, + layout_children, + cursor, + viewport, + ); + + let state = tree.state.downcast_ref::(); + if !state.window_has_focus { + let header_bar_appearance = + ::appearance( + theme, + &crate::theme::Container::HeaderBar, + ); + let cosmic = theme.cosmic(); + let mut neutral_0 = cosmic.palette.neutral_0; + neutral_0.alpha = 0.3; + + // draw overlay rectangle + renderer.fill_quad( + Quad { + bounds: layout.bounds(), + border_radius: header_bar_appearance.border_radius, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color(neutral_0.into()), + ); + } + } + + fn on_event( + &mut self, + state: &mut tree::Tree, + event: iced_core::Event, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn iced_core::Clipboard, + shell: &mut iced_core::Shell<'_, Message>, + viewport: &iced_core::Rectangle, + ) -> iced_core::event::Status { + if let iced_core::Event::Window(id, iced_core::window::Event::Focused) = event { + let state = state.state.downcast_mut::(); + state.focus_window(id); + } else if let iced_core::Event::Window(id, iced_core::window::Event::Unfocused) = event { + let state = state.state.downcast_mut::(); + state.unfocus_window(id); + } + + let child_state = &mut state.children[0]; + let child_layout = layout.children().next().unwrap(); + self.header_bar_inner.as_widget_mut().on_event( + child_state, + event, + child_layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + state: &tree::Tree, + layout: iced_core::Layout<'_>, + cursor: iced_core::mouse::Cursor, + viewport: &iced_core::Rectangle, + renderer: &crate::Renderer, + ) -> iced_core::mouse::Interaction { + let child_tree = &state.children[0]; + let child_layout = layout.children().next().unwrap(); + self.header_bar_inner.as_widget().mouse_interaction( + child_tree, + child_layout, + cursor, + viewport, + renderer, + ) + } + + fn operate( + &self, + state: &mut tree::Tree, + layout: iced_core::Layout<'_>, + renderer: &crate::Renderer, + operation: &mut dyn iced_core::widget::Operation< + iced_core::widget::OperationOutputWrapper, + >, + ) { + let child_tree = &mut state.children[0]; + let child_layout = layout.children().next().unwrap(); + self.header_bar_inner + .as_widget() + .operate(child_tree, child_layout, renderer, operation); + } + + fn overlay<'b>( + &'b mut self, + state: &'b mut tree::Tree, + layout: iced_core::Layout<'_>, + renderer: &crate::Renderer, + ) -> Option> { + let child_tree = &mut state.children[0]; + let child_layout = layout.children().next().unwrap(); + self.header_bar_inner + .as_widget_mut() + .overlay(child_tree, child_layout, renderer) + } } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -207,6 +401,40 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { fn from(headerbar: HeaderBar<'a, Message>) -> Self { - headerbar.into_element() + Element::new(headerbar.build()) + } +} + +impl<'a, Message: Clone + 'static> From> for Element<'a, Message> { + fn from(headerbar: HeaderBarWidget<'a, Message>) -> Self { + Element::new(headerbar) + } +} + +pub struct MyState { + pub window_id: Option, + pub window_has_focus: bool, +} + +impl MyState { + pub fn new(id: Option) -> Self { + Self { + window_id: id, + window_has_focus: id.is_none(), + } + } + + pub fn focus_window(&mut self, id: window::Id) { + if self.window_id != Some(id) { + return; + } + self.window_has_focus = true; + } + + pub fn unfocus_window(&mut self, id: window::Id) { + if self.window_id != Some(id) { + return; + } + self.window_has_focus = false; } }