-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(examples): Add HeaderBar example as MouseListener example
- Loading branch information
Showing
4 changed files
with
380 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[package] | ||
name = "headerbar" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
apply = "0.3.0" | ||
derive_setters = "0.1.5" | ||
iced = { path = "../.." } | ||
iced_native = { path = "../../native" } | ||
iced_winit = { path = "../../winit" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// Copyright 2022 System76 <[email protected]> | ||
// SPDX-License-Identifier: MIT | ||
|
||
use apply::Apply; | ||
use derive_setters::Setters; | ||
use iced::alignment::{Horizontal, Vertical}; | ||
use iced::widget::{column, container, mouse_listener, text}; | ||
use iced::{Application, Color, Element, Length}; | ||
use std::borrow::Cow; | ||
|
||
#[derive(Default, Setters)] | ||
pub struct App { | ||
#[setters(into, rename = "with_title")] | ||
title: String, | ||
#[setters(skip)] | ||
exit: bool, | ||
#[setters(skip)] | ||
mouse_inside_listener: bool, | ||
#[setters(skip)] | ||
listener_state: Option<ListenerState>, | ||
} | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub enum Message { | ||
Close, | ||
Drag, | ||
Maximize, | ||
Minimize, | ||
ListenerEntered, | ||
ListenerExited, | ||
ListenerState(ListenerState), | ||
} | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub enum ListenerState { | ||
Pressed, | ||
Released, | ||
RightPressed, | ||
RightReleased, | ||
MiddlePressed, | ||
MiddleReleased, | ||
} | ||
|
||
impl Application for App { | ||
type Executor = iced::executor::Default; | ||
type Flags = (); | ||
type Message = Message; | ||
type Theme = iced::Theme; | ||
|
||
fn new(_flags: ()) -> (Self, iced::Command<Message>) { | ||
let app = App::default().with_title("Headerbar Example"); | ||
|
||
(app, iced::Command::none()) | ||
} | ||
|
||
fn title(&self) -> String { | ||
self.title.clone() | ||
} | ||
|
||
fn update(&mut self, message: Message) -> iced::Command<Message> { | ||
match message { | ||
Message::Close => self.exit = true, | ||
Message::Drag => return iced_winit::window::drag(), | ||
Message::Maximize => return iced_winit::window::toggle_maximize(), | ||
Message::Minimize => return iced_winit::window::minimize(true), | ||
Message::ListenerEntered => self.mouse_inside_listener = true, | ||
Message::ListenerExited => { | ||
self.mouse_inside_listener = false; | ||
self.listener_state = None; | ||
} | ||
Message::ListenerState(state) => self.listener_state = Some(state), | ||
} | ||
|
||
iced::Command::none() | ||
} | ||
|
||
fn view(&self) -> Element<Message> { | ||
let listener_text = match self.listener_state { | ||
Some(state) => Cow::Owned(format!("{:?}", state)), | ||
None => Cow::Borrowed("Press mouse buttons here"), | ||
}; | ||
|
||
column(vec![ | ||
// Attach a headerbar to the top of the window. | ||
crate::headerbar::header_bar::<Message, iced::Renderer>() | ||
.title(&self.title) | ||
.container_style(iced::theme::Container::custom_fn( | ||
header_container_style, | ||
)) | ||
.button_style(|| iced::theme::Button::Secondary) | ||
.on_drag(Message::Drag) | ||
.on_close(Message::Close) | ||
.on_minimize(Message::Minimize) | ||
.on_maximize(Message::Maximize) | ||
.apply(Element::from), | ||
// Then attach the content area beneath the headerbar. | ||
text(listener_text) | ||
.horizontal_alignment(Horizontal::Center) | ||
.vertical_alignment(Vertical::Center) | ||
.width(Length::Fill) | ||
.height(Length::Fill) | ||
// Wrap text in a 200x100 container. | ||
.apply(container) | ||
.width(Length::Units(200)) | ||
.height(Length::Units(100)) | ||
.style(iced::theme::Container::custom_fn( | ||
if self.mouse_inside_listener { | ||
mouse_inside_style | ||
} else { | ||
header_container_style | ||
}, | ||
)) | ||
// Listen to mouse events on the container. | ||
.apply(mouse_listener) | ||
.on_mouse_enter(Message::ListenerEntered) | ||
.on_mouse_exit(Message::ListenerExited) | ||
.on_press(Message::ListenerState(ListenerState::Pressed)) | ||
.on_release(Message::ListenerState(ListenerState::Released)) | ||
.on_right_press(Message::ListenerState( | ||
ListenerState::RightPressed, | ||
)) | ||
.on_right_release(Message::ListenerState( | ||
ListenerState::RightReleased, | ||
)) | ||
.on_middle_press(Message::ListenerState( | ||
ListenerState::MiddlePressed, | ||
)) | ||
.on_middle_release(Message::ListenerState( | ||
ListenerState::MiddleReleased, | ||
)) | ||
// Then center this container in the middle of the app. | ||
.apply(container) | ||
.width(Length::Fill) | ||
.height(Length::Fill) | ||
.center_x() | ||
.center_y() | ||
.apply(Element::from), | ||
]) | ||
.into() | ||
} | ||
|
||
fn should_exit(&self) -> bool { | ||
self.exit | ||
} | ||
} | ||
|
||
fn header_container_style( | ||
_theme: &iced::Theme, | ||
) -> iced::widget::container::Appearance { | ||
iced::widget::container::Appearance { | ||
text_color: Some(iced::color!(0xffffff)), | ||
background: Some(iced::color!(0x333333).into()), | ||
border_radius: 0.0, | ||
border_width: 0.0, | ||
border_color: Color::TRANSPARENT, | ||
} | ||
} | ||
|
||
fn mouse_inside_style( | ||
_theme: &iced::Theme, | ||
) -> iced::widget::container::Appearance { | ||
iced::widget::container::Appearance { | ||
text_color: Some(iced::color!(0xffffff)), | ||
background: Some(iced::color!(0x555555).into()), | ||
border_radius: 0.0, | ||
border_width: 0.0, | ||
border_color: Color::TRANSPARENT, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
// Copyright 2022 System76 <[email protected]> | ||
// SPDX-License-Identifier: MIT | ||
|
||
use apply::Apply; | ||
use derive_setters::Setters; | ||
use iced::alignment::{Horizontal, Vertical}; | ||
use iced::{self, widget, Element, Length}; | ||
use std::borrow::Cow; | ||
|
||
use iced::widget::button::StyleSheet as ButtonStylesheet; | ||
use iced::widget::container::StyleSheet as ContainerStylesheet; | ||
use iced::widget::text::StyleSheet as TextStylesheet; | ||
|
||
type ButtonStyle<Renderer> = | ||
<<Renderer as iced_native::Renderer>::Theme as ButtonStylesheet>::Style; | ||
type ContainerStyle<Renderer> = | ||
<<Renderer as iced_native::Renderer>::Theme as ContainerStylesheet>::Style; | ||
|
||
#[allow(clippy::redundant_closure)] | ||
#[must_use] | ||
pub fn header_bar<'a, Message, Renderer>() -> HeaderBar<'a, Message, Renderer> | ||
where | ||
Message: Clone + 'static, | ||
Renderer: iced_native::Renderer, | ||
Renderer::Theme: ButtonStylesheet + ContainerStylesheet, | ||
{ | ||
HeaderBar { | ||
title: Cow::from(""), | ||
button_style: Box::new(|| ButtonStyle::<Renderer>::default()), | ||
container_style: ContainerStyle::<Renderer>::default(), | ||
on_close: None, | ||
on_drag: None, | ||
on_maximize: None, | ||
on_minimize: None, | ||
start: None, | ||
center: None, | ||
end: None, | ||
} | ||
} | ||
|
||
#[derive(Setters)] | ||
pub struct HeaderBar<'a, Message, Renderer> | ||
where | ||
Renderer: iced_native::Renderer, | ||
Renderer::Theme: ButtonStylesheet + ContainerStylesheet, | ||
{ | ||
#[setters(into)] | ||
title: Cow<'a, str>, | ||
#[setters(into)] | ||
container_style: ContainerStyle<Renderer>, | ||
#[setters(skip)] | ||
button_style: Box<dyn Fn() -> ButtonStyle<Renderer> + 'static>, | ||
#[setters(strip_option)] | ||
on_close: Option<Message>, | ||
#[setters(strip_option)] | ||
on_drag: Option<Message>, | ||
#[setters(strip_option)] | ||
on_maximize: Option<Message>, | ||
#[setters(strip_option)] | ||
on_minimize: Option<Message>, | ||
#[setters(strip_option)] | ||
start: Option<Element<'a, Message, Renderer>>, | ||
#[setters(strip_option)] | ||
center: Option<Element<'a, Message, Renderer>>, | ||
#[setters(strip_option)] | ||
end: Option<Element<'a, Message, Renderer>>, | ||
} | ||
|
||
impl<'a, Message, Renderer> HeaderBar<'a, Message, Renderer> | ||
where | ||
Message: Clone + 'static, | ||
Renderer: iced_native::Renderer + iced_native::text::Renderer + 'static, | ||
Renderer::Theme: ButtonStylesheet + ContainerStylesheet + TextStylesheet, | ||
{ | ||
pub fn button_style( | ||
mut self, | ||
style: impl Fn() -> ButtonStyle<Renderer> + 'static, | ||
) -> Self { | ||
self.button_style = Box::new(style); | ||
self | ||
} | ||
|
||
/// Converts the headerbar builder into an Iced element. | ||
pub fn into_element(mut self) -> Element<'a, Message, Renderer> { | ||
let mut packed: Vec<Element<Message, Renderer>> = Vec::with_capacity(4); | ||
|
||
if let Some(start) = self.start.take() { | ||
packed.push( | ||
widget::container(start) | ||
.align_x(iced::alignment::Horizontal::Left) | ||
.into(), | ||
); | ||
} | ||
|
||
packed.push(if let Some(center) = self.center.take() { | ||
widget::container(center) | ||
.align_x(iced::alignment::Horizontal::Center) | ||
.into() | ||
} else { | ||
self.title_widget().into() | ||
}); | ||
|
||
packed.push(if let Some(end) = self.end.take() { | ||
widget::row(vec![end, self.window_controls()]) | ||
.apply(widget::container) | ||
.align_x(iced::alignment::Horizontal::Right) | ||
.into() | ||
} else { | ||
self.window_controls() | ||
}); | ||
|
||
let mut widget = widget::row(packed) | ||
.height(Length::Units(50)) | ||
.padding(10) | ||
.apply(widget::container) | ||
.center_y() | ||
.style(self.container_style) | ||
.apply(widget::mouse_listener); | ||
|
||
if let Some(message) = self.on_drag.take() { | ||
widget = widget.on_press(message); | ||
} | ||
|
||
if let Some(message) = self.on_maximize.take() { | ||
widget = widget.on_release(message); | ||
} | ||
|
||
widget.into() | ||
} | ||
|
||
fn title_widget(&self) -> iced::widget::Container<'a, Message, Renderer> { | ||
widget::container(widget::text(&self.title)) | ||
.center_x() | ||
.center_y() | ||
.width(Length::Fill) | ||
.height(Length::Fill) | ||
} | ||
|
||
/// Creates the widget for window controls. | ||
fn window_controls(&mut self) -> Element<'a, Message, Renderer> { | ||
let mut widgets: Vec<Element<Message, Renderer>> = | ||
Vec::with_capacity(3); | ||
|
||
let button = |text, size, on_press| { | ||
iced::widget::text(text) | ||
.height(Length::Units(size)) | ||
.width(Length::Units(size)) | ||
.vertical_alignment(Vertical::Center) | ||
.horizontal_alignment(Horizontal::Center) | ||
.apply(iced::widget::button) | ||
.style((self.button_style)()) | ||
.on_press(on_press) | ||
}; | ||
|
||
if let Some(message) = self.on_minimize.take() { | ||
widgets.push(button("-", 24, message).into()); | ||
} | ||
|
||
if let Some(message) = self.on_maximize.clone() { | ||
widgets.push(button("[]", 24, message).into()); | ||
} | ||
|
||
if let Some(message) = self.on_close.take() { | ||
widgets.push(button("x", 24, message).into()); | ||
} | ||
|
||
widget::row(widgets) | ||
.spacing(8) | ||
.apply(widget::container) | ||
.height(Length::Fill) | ||
.center_y() | ||
.into() | ||
} | ||
} | ||
|
||
impl<'a, Message, Renderer> From<HeaderBar<'a, Message, Renderer>> | ||
for Element<'a, Message, Renderer> | ||
where | ||
Message: Clone + 'static, | ||
Renderer: iced_native::Renderer + iced_native::text::Renderer + 'static, | ||
Renderer::Theme: ButtonStylesheet + ContainerStylesheet + TextStylesheet, | ||
{ | ||
fn from(headerbar: HeaderBar<'a, Message, Renderer>) -> Self { | ||
headerbar.into_element() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copyright 2022 System76 <[email protected]> | ||
// SPDX-License-Identifier: MIT | ||
|
||
mod app; | ||
mod headerbar; | ||
|
||
use self::app::App; | ||
use iced::Application; | ||
|
||
fn main() -> iced::Result { | ||
App::run(iced::Settings::default()) | ||
} |