Skip to content

Commit 06611ce

Browse files
committed
chore(examples): Add HeaderBar example as MouseListener example
1 parent d09d90d commit 06611ce

File tree

4 files changed

+380
-0
lines changed

4 files changed

+380
-0
lines changed

examples/headerbar/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "headerbar"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
apply = "0.3.0"
10+
derive_setters = "0.1.5"
11+
iced = { path = "../.." }
12+
iced_native = { path = "../../native" }
13+
iced_winit = { path = "../../winit" }

examples/headerbar/src/app.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2022 System76 <[email protected]>
2+
// SPDX-License-Identifier: MIT
3+
4+
use apply::Apply;
5+
use derive_setters::Setters;
6+
use iced::alignment::{Horizontal, Vertical};
7+
use iced::widget::{column, container, mouse_listener, text};
8+
use iced::{Application, Color, Element, Length};
9+
use std::borrow::Cow;
10+
11+
#[derive(Default, Setters)]
12+
pub struct App {
13+
#[setters(into, rename = "with_title")]
14+
title: String,
15+
#[setters(skip)]
16+
exit: bool,
17+
#[setters(skip)]
18+
mouse_inside_listener: bool,
19+
#[setters(skip)]
20+
listener_state: Option<ListenerState>,
21+
}
22+
23+
#[derive(Clone, Copy, Debug)]
24+
pub enum Message {
25+
Close,
26+
Drag,
27+
Maximize,
28+
Minimize,
29+
ListenerEntered,
30+
ListenerExited,
31+
ListenerState(ListenerState),
32+
}
33+
34+
#[derive(Clone, Copy, Debug)]
35+
pub enum ListenerState {
36+
Pressed,
37+
Released,
38+
RightPressed,
39+
RightReleased,
40+
MiddlePressed,
41+
MiddleReleased,
42+
}
43+
44+
impl Application for App {
45+
type Executor = iced::executor::Default;
46+
type Flags = ();
47+
type Message = Message;
48+
type Theme = iced::Theme;
49+
50+
fn new(_flags: ()) -> (Self, iced::Command<Message>) {
51+
let app = App::default().with_title("Headerbar Example");
52+
53+
(app, iced::Command::none())
54+
}
55+
56+
fn title(&self) -> String {
57+
self.title.clone()
58+
}
59+
60+
fn update(&mut self, message: Message) -> iced::Command<Message> {
61+
match message {
62+
Message::Close => self.exit = true,
63+
Message::Drag => return iced_winit::window::drag(),
64+
Message::Maximize => return iced_winit::window::toggle_maximize(),
65+
Message::Minimize => return iced_winit::window::minimize(true),
66+
Message::ListenerEntered => self.mouse_inside_listener = true,
67+
Message::ListenerExited => {
68+
self.mouse_inside_listener = false;
69+
self.listener_state = None;
70+
}
71+
Message::ListenerState(state) => self.listener_state = Some(state),
72+
}
73+
74+
iced::Command::none()
75+
}
76+
77+
fn view(&self) -> Element<Message> {
78+
let listener_text = match self.listener_state {
79+
Some(state) => Cow::Owned(format!("{:?}", state)),
80+
None => Cow::Borrowed("Press mouse buttons here"),
81+
};
82+
83+
column(vec![
84+
// Attach a headerbar to the top of the window.
85+
crate::headerbar::header_bar::<Message, iced::Renderer>()
86+
.title(&self.title)
87+
.container_style(iced::theme::Container::custom_fn(
88+
header_container_style,
89+
))
90+
.button_style(|| iced::theme::Button::Secondary)
91+
.on_drag(Message::Drag)
92+
.on_close(Message::Close)
93+
.on_minimize(Message::Minimize)
94+
.on_maximize(Message::Maximize)
95+
.apply(Element::from),
96+
// Then attach the content area beneath the headerbar.
97+
text(listener_text)
98+
.horizontal_alignment(Horizontal::Center)
99+
.vertical_alignment(Vertical::Center)
100+
.width(Length::Fill)
101+
.height(Length::Fill)
102+
// Wrap text in a 200x100 container.
103+
.apply(container)
104+
.width(Length::Units(200))
105+
.height(Length::Units(100))
106+
.style(iced::theme::Container::custom_fn(
107+
if self.mouse_inside_listener {
108+
mouse_inside_style
109+
} else {
110+
header_container_style
111+
},
112+
))
113+
// Listen to mouse events on the container.
114+
.apply(mouse_listener)
115+
.on_mouse_enter(Message::ListenerEntered)
116+
.on_mouse_exit(Message::ListenerExited)
117+
.on_press(Message::ListenerState(ListenerState::Pressed))
118+
.on_release(Message::ListenerState(ListenerState::Released))
119+
.on_right_press(Message::ListenerState(
120+
ListenerState::RightPressed,
121+
))
122+
.on_right_release(Message::ListenerState(
123+
ListenerState::RightReleased,
124+
))
125+
.on_middle_press(Message::ListenerState(
126+
ListenerState::MiddlePressed,
127+
))
128+
.on_middle_release(Message::ListenerState(
129+
ListenerState::MiddleReleased,
130+
))
131+
// Then center this container in the middle of the app.
132+
.apply(container)
133+
.width(Length::Fill)
134+
.height(Length::Fill)
135+
.center_x()
136+
.center_y()
137+
.apply(Element::from),
138+
])
139+
.into()
140+
}
141+
142+
fn should_exit(&self) -> bool {
143+
self.exit
144+
}
145+
}
146+
147+
fn header_container_style(
148+
_theme: &iced::Theme,
149+
) -> iced::widget::container::Appearance {
150+
iced::widget::container::Appearance {
151+
text_color: Some(iced::color!(0xffffff)),
152+
background: Some(iced::color!(0x333333).into()),
153+
border_radius: 0.0,
154+
border_width: 0.0,
155+
border_color: Color::TRANSPARENT,
156+
}
157+
}
158+
159+
fn mouse_inside_style(
160+
_theme: &iced::Theme,
161+
) -> iced::widget::container::Appearance {
162+
iced::widget::container::Appearance {
163+
text_color: Some(iced::color!(0xffffff)),
164+
background: Some(iced::color!(0x555555).into()),
165+
border_radius: 0.0,
166+
border_width: 0.0,
167+
border_color: Color::TRANSPARENT,
168+
}
169+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2022 System76 <[email protected]>
2+
// SPDX-License-Identifier: MIT
3+
4+
use apply::Apply;
5+
use derive_setters::Setters;
6+
use iced::alignment::{Horizontal, Vertical};
7+
use iced::{self, widget, Element, Length};
8+
use std::borrow::Cow;
9+
10+
use iced::widget::button::StyleSheet as ButtonStylesheet;
11+
use iced::widget::container::StyleSheet as ContainerStylesheet;
12+
use iced::widget::text::StyleSheet as TextStylesheet;
13+
14+
type ButtonStyle<Renderer> =
15+
<<Renderer as iced_native::Renderer>::Theme as ButtonStylesheet>::Style;
16+
type ContainerStyle<Renderer> =
17+
<<Renderer as iced_native::Renderer>::Theme as ContainerStylesheet>::Style;
18+
19+
#[allow(clippy::redundant_closure)]
20+
#[must_use]
21+
pub fn header_bar<'a, Message, Renderer>() -> HeaderBar<'a, Message, Renderer>
22+
where
23+
Message: Clone + 'static,
24+
Renderer: iced_native::Renderer,
25+
Renderer::Theme: ButtonStylesheet + ContainerStylesheet,
26+
{
27+
HeaderBar {
28+
title: Cow::from(""),
29+
button_style: Box::new(|| ButtonStyle::<Renderer>::default()),
30+
container_style: ContainerStyle::<Renderer>::default(),
31+
on_close: None,
32+
on_drag: None,
33+
on_maximize: None,
34+
on_minimize: None,
35+
start: None,
36+
center: None,
37+
end: None,
38+
}
39+
}
40+
41+
#[derive(Setters)]
42+
pub struct HeaderBar<'a, Message, Renderer>
43+
where
44+
Renderer: iced_native::Renderer,
45+
Renderer::Theme: ButtonStylesheet + ContainerStylesheet,
46+
{
47+
#[setters(into)]
48+
title: Cow<'a, str>,
49+
#[setters(into)]
50+
container_style: ContainerStyle<Renderer>,
51+
#[setters(skip)]
52+
button_style: Box<dyn Fn() -> ButtonStyle<Renderer> + 'static>,
53+
#[setters(strip_option)]
54+
on_close: Option<Message>,
55+
#[setters(strip_option)]
56+
on_drag: Option<Message>,
57+
#[setters(strip_option)]
58+
on_maximize: Option<Message>,
59+
#[setters(strip_option)]
60+
on_minimize: Option<Message>,
61+
#[setters(strip_option)]
62+
start: Option<Element<'a, Message, Renderer>>,
63+
#[setters(strip_option)]
64+
center: Option<Element<'a, Message, Renderer>>,
65+
#[setters(strip_option)]
66+
end: Option<Element<'a, Message, Renderer>>,
67+
}
68+
69+
impl<'a, Message, Renderer> HeaderBar<'a, Message, Renderer>
70+
where
71+
Message: Clone + 'static,
72+
Renderer: iced_native::Renderer + iced_native::text::Renderer + 'static,
73+
Renderer::Theme: ButtonStylesheet + ContainerStylesheet + TextStylesheet,
74+
{
75+
pub fn button_style(
76+
mut self,
77+
style: impl Fn() -> ButtonStyle<Renderer> + 'static,
78+
) -> Self {
79+
self.button_style = Box::new(style);
80+
self
81+
}
82+
83+
/// Converts the headerbar builder into an Iced element.
84+
pub fn into_element(mut self) -> Element<'a, Message, Renderer> {
85+
let mut packed: Vec<Element<Message, Renderer>> = Vec::with_capacity(4);
86+
87+
if let Some(start) = self.start.take() {
88+
packed.push(
89+
widget::container(start)
90+
.align_x(iced::alignment::Horizontal::Left)
91+
.into(),
92+
);
93+
}
94+
95+
packed.push(if let Some(center) = self.center.take() {
96+
widget::container(center)
97+
.align_x(iced::alignment::Horizontal::Center)
98+
.into()
99+
} else {
100+
self.title_widget().into()
101+
});
102+
103+
packed.push(if let Some(end) = self.end.take() {
104+
widget::row(vec![end, self.window_controls()])
105+
.apply(widget::container)
106+
.align_x(iced::alignment::Horizontal::Right)
107+
.into()
108+
} else {
109+
self.window_controls()
110+
});
111+
112+
let mut widget = widget::row(packed)
113+
.height(Length::Units(50))
114+
.padding(10)
115+
.apply(widget::container)
116+
.center_y()
117+
.style(self.container_style)
118+
.apply(widget::mouse_listener);
119+
120+
if let Some(message) = self.on_drag.take() {
121+
widget = widget.on_press(message);
122+
}
123+
124+
if let Some(message) = self.on_maximize.take() {
125+
widget = widget.on_release(message);
126+
}
127+
128+
widget.into()
129+
}
130+
131+
fn title_widget(&self) -> iced::widget::Container<'a, Message, Renderer> {
132+
widget::container(widget::text(&self.title))
133+
.center_x()
134+
.center_y()
135+
.width(Length::Fill)
136+
.height(Length::Fill)
137+
}
138+
139+
/// Creates the widget for window controls.
140+
fn window_controls(&mut self) -> Element<'a, Message, Renderer> {
141+
let mut widgets: Vec<Element<Message, Renderer>> =
142+
Vec::with_capacity(3);
143+
144+
let button = |text, size, on_press| {
145+
iced::widget::text(text)
146+
.height(Length::Units(size))
147+
.width(Length::Units(size))
148+
.vertical_alignment(Vertical::Center)
149+
.horizontal_alignment(Horizontal::Center)
150+
.apply(iced::widget::button)
151+
.style((self.button_style)())
152+
.on_press(on_press)
153+
};
154+
155+
if let Some(message) = self.on_minimize.take() {
156+
widgets.push(button("-", 24, message).into());
157+
}
158+
159+
if let Some(message) = self.on_maximize.clone() {
160+
widgets.push(button("[]", 24, message).into());
161+
}
162+
163+
if let Some(message) = self.on_close.take() {
164+
widgets.push(button("x", 24, message).into());
165+
}
166+
167+
widget::row(widgets)
168+
.spacing(8)
169+
.apply(widget::container)
170+
.height(Length::Fill)
171+
.center_y()
172+
.into()
173+
}
174+
}
175+
176+
impl<'a, Message, Renderer> From<HeaderBar<'a, Message, Renderer>>
177+
for Element<'a, Message, Renderer>
178+
where
179+
Message: Clone + 'static,
180+
Renderer: iced_native::Renderer + iced_native::text::Renderer + 'static,
181+
Renderer::Theme: ButtonStylesheet + ContainerStylesheet + TextStylesheet,
182+
{
183+
fn from(headerbar: HeaderBar<'a, Message, Renderer>) -> Self {
184+
headerbar.into_element()
185+
}
186+
}

examples/headerbar/src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2022 System76 <[email protected]>
2+
// SPDX-License-Identifier: MIT
3+
4+
mod app;
5+
mod headerbar;
6+
7+
use self::app::App;
8+
use iced::Application;
9+
10+
fn main() -> iced::Result {
11+
App::run(iced::Settings::default())
12+
}

0 commit comments

Comments
 (0)