Skip to content

Commit

Permalink
chore(examples): Add HeaderBar example as MouseListener example
Browse files Browse the repository at this point in the history
  • Loading branch information
mmstick committed Dec 22, 2022
1 parent 4657349 commit 120c7eb
Show file tree
Hide file tree
Showing 4 changed files with 380 additions and 0 deletions.
13 changes: 13 additions & 0 deletions examples/headerbar/Cargo.toml
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" }
169 changes: 169 additions & 0 deletions examples/headerbar/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2022 System76 <info@system76.com>
// 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,
}
}
186 changes: 186 additions & 0 deletions examples/headerbar/src/headerbar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright 2022 System76 <info@system76.com>
// 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()
}
}
12 changes: 12 additions & 0 deletions examples/headerbar/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MIT

mod app;
mod headerbar;

use self::app::App;
use iced::Application;

fn main() -> iced::Result {
App::run(iced::Settings::default())
}

0 comments on commit 120c7eb

Please sign in to comment.