summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-07-23 12:07:32 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-07-23 12:07:32 +0200
commit666e6761bcabf454d0a5d8c35a6fefc1e49a16aa (patch)
treeb426ed1e5c258bdcb9cf16cf0c3f3d20364b07a3 /examples
parent8f6ea4bdc99ef5960316d2230676495f2d90c30e (diff)
downloadiced-666e6761bcabf454d0a5d8c35a6fefc1e49a16aa.tar.gz
iced-666e6761bcabf454d0a5d8c35a6fefc1e49a16aa.tar.bz2
iced-666e6761bcabf454d0a5d8c35a6fefc1e49a16aa.zip
Show Coffee's UI tour in `ggez` example
Diffstat (limited to 'examples')
-rw-r--r--examples/ggez/main.rs101
-rw-r--r--examples/ggez/tour.rs545
2 files changed, 618 insertions, 28 deletions
diff --git a/examples/ggez/main.rs b/examples/ggez/main.rs
index 3c02157a..8f1393ab 100644
--- a/examples/ggez/main.rs
+++ b/examples/ggez/main.rs
@@ -1,8 +1,10 @@
mod renderer;
+mod tour;
mod widget;
use renderer::Renderer;
-use widget::{button, Button, Checkbox, Column, Text};
+use tour::Tour;
+use widget::Column;
use ggez;
use ggez::event;
@@ -10,7 +12,13 @@ use ggez::graphics;
use ggez::input::mouse;
pub fn main() -> ggez::GameResult {
- let cb = ggez::ContextBuilder::new("iced", "ggez");
+ let cb = ggez::ContextBuilder::new("iced", "ggez").window_mode(
+ ggez::conf::WindowMode {
+ width: 1280.0,
+ height: 1024.0,
+ ..ggez::conf::WindowMode::default()
+ },
+ );
let (ctx, event_loop) = &mut cb.build()?;
let state = &mut Game::new(ctx)?;
event::run(ctx, event_loop, state)
@@ -20,16 +28,18 @@ struct Game {
spritesheet: graphics::Image,
runtime: iced::Runtime,
- button: button::State,
+ tour: Tour,
}
impl Game {
fn new(context: &mut ggez::Context) -> ggez::GameResult<Game> {
+ graphics::set_default_filter(context, graphics::FilterMode::Nearest);
+
Ok(Game {
spritesheet: graphics::Image::new(context, "/ui.png").unwrap(),
runtime: iced::Runtime::new(),
- button: button::State::new(),
+ tour: Tour::new(),
})
}
}
@@ -39,6 +49,36 @@ impl event::EventHandler for Game {
Ok(())
}
+ fn mouse_button_down_event(
+ &mut self,
+ _context: &mut ggez::Context,
+ button: mouse::MouseButton,
+ _x: f32,
+ _y: f32,
+ ) {
+ self.runtime.on_event(iced::Event::Mouse(
+ iced::input::mouse::Event::Input {
+ state: iced::input::ButtonState::Pressed,
+ button: iced::input::mouse::Button::Left,
+ },
+ ));
+ }
+
+ fn mouse_button_up_event(
+ &mut self,
+ _context: &mut ggez::Context,
+ button: mouse::MouseButton,
+ _x: f32,
+ _y: f32,
+ ) {
+ self.runtime.on_event(iced::Event::Mouse(
+ iced::input::mouse::Event::Input {
+ state: iced::input::ButtonState::Released,
+ button: iced::input::mouse::Button::Left,
+ },
+ ));
+ }
+
fn mouse_motion_event(
&mut self,
_context: &mut ggez::Context,
@@ -52,34 +92,40 @@ impl event::EventHandler for Game {
));
}
- fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult {
- graphics::clear(context, [0.1, 0.2, 0.3, 1.0].into());
-
- let screen = graphics::screen_coordinates(context);
+ fn resize_event(
+ &mut self,
+ context: &mut ggez::Context,
+ width: f32,
+ height: f32,
+ ) {
+ graphics::set_screen_coordinates(
+ context,
+ graphics::Rect {
+ x: 0.0,
+ y: 0.0,
+ w: width,
+ h: height,
+ },
+ )
+ .expect("Set screen coordinates");
+ }
- let cursor = {
- let hello = Text::new("Hello, iced!");
+ fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult {
+ graphics::clear(context, [0.3, 0.3, 0.6, 1.0].into());
- let checkbox =
- Checkbox::new(true, "Check me!", Message::CheckboxToggled);
+ self.tour.draw(context).expect("Draw tour");
- let button = Button::new(&mut self.button, "Press me!")
- .width(200)
- .align_self(iced::Align::End);
+ let screen = graphics::screen_coordinates(context);
- let widgets = Column::new()
- .max_width(600)
- .spacing(20)
- .push(hello)
- .push(checkbox)
- .push(button);
+ let (messages, cursor) = {
+ let layout = self.tour.layout();
let content = Column::new()
.width(screen.w as u32)
.height(screen.h as u32)
.align_items(iced::Align::Center)
.justify_content(iced::Justify::Center)
- .push(widgets);
+ .push(layout);
let renderer =
&mut Renderer::new(context, self.spritesheet.clone());
@@ -91,9 +137,13 @@ impl event::EventHandler for Game {
renderer.flush();
- cursor
+ (messages, cursor)
};
+ for message in messages {
+ self.tour.react(message);
+ }
+
mouse::set_cursor_type(context, into_cursor_type(cursor));
graphics::present(context)?;
@@ -101,11 +151,6 @@ impl event::EventHandler for Game {
}
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum Message {
- CheckboxToggled(bool),
-}
-
fn into_cursor_type(cursor: iced::MouseCursor) -> mouse::MouseCursor {
match cursor {
iced::MouseCursor::OutOfBounds => mouse::MouseCursor::Default,
diff --git a/examples/ggez/tour.rs b/examples/ggez/tour.rs
new file mode 100644
index 00000000..3808fdda
--- /dev/null
+++ b/examples/ggez/tour.rs
@@ -0,0 +1,545 @@
+use super::widget::{
+ button, slider, Button, Checkbox, Column, Element, Radio, Row, Slider, Text,
+};
+
+use ggez::graphics::{Color, BLACK};
+use iced::{text::HorizontalAlignment, Align};
+
+pub struct Tour {
+ steps: Steps,
+ back_button: button::State,
+ next_button: button::State,
+}
+
+impl Tour {
+ pub fn new() -> Tour {
+ Tour {
+ steps: Steps::new(),
+ back_button: button::State::new(),
+ next_button: button::State::new(),
+ }
+ }
+
+ pub fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult {
+ Ok(())
+ }
+
+ pub fn react(&mut self, event: Message) {
+ match event {
+ Message::BackPressed => {
+ self.steps.go_back();
+ }
+ Message::NextPressed => {
+ self.steps.advance();
+ }
+ Message::StepMessage(step_msg) => {
+ self.steps.update(step_msg);
+ }
+ }
+ }
+
+ pub fn layout(&mut self) -> Element<Message> {
+ let Tour {
+ steps,
+ back_button,
+ next_button,
+ } = self;
+
+ let mut controls = Row::new();
+
+ if steps.has_previous() {
+ controls = controls.push(
+ Button::new(back_button, "Back")
+ .on_press(Message::BackPressed)
+ .class(button::Class::Secondary),
+ );
+ }
+
+ controls = controls.push(Column::new());
+
+ if steps.can_continue() {
+ controls = controls.push(
+ Button::new(next_button, "Next").on_press(Message::NextPressed),
+ );
+ }
+
+ Column::new()
+ .max_width(500)
+ .spacing(20)
+ .push(steps.layout().map(Message::StepMessage))
+ .push(controls)
+ .into()
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Message {
+ BackPressed,
+ NextPressed,
+ StepMessage(StepMessage),
+}
+
+struct Steps {
+ steps: Vec<Step>,
+ current: usize,
+}
+
+impl Steps {
+ fn new() -> Steps {
+ Steps {
+ steps: vec![
+ Step::Welcome,
+ Step::Buttons {
+ primary: button::State::new(),
+ secondary: button::State::new(),
+ positive: button::State::new(),
+ },
+ Step::Checkbox { is_checked: false },
+ Step::Radio { selection: None },
+ Step::Slider {
+ state: slider::State::new(),
+ value: 50,
+ },
+ Step::Text {
+ size_slider: slider::State::new(),
+ size: 30,
+ color_sliders: [slider::State::new(); 3],
+ color: BLACK,
+ },
+ Step::RowsAndColumns {
+ layout: Layout::Row,
+ spacing_slider: slider::State::new(),
+ spacing: 20,
+ },
+ Step::End,
+ ],
+ current: 0,
+ }
+ }
+
+ fn update(&mut self, msg: StepMessage) {
+ self.steps[self.current].update(msg);
+ }
+
+ fn layout(&mut self) -> Element<StepMessage> {
+ self.steps[self.current].layout()
+ }
+
+ fn advance(&mut self) {
+ if self.can_continue() {
+ self.current += 1;
+ }
+ }
+
+ fn go_back(&mut self) {
+ if self.has_previous() {
+ self.current -= 1;
+ }
+ }
+
+ fn has_previous(&self) -> bool {
+ self.current > 0
+ }
+
+ fn can_continue(&self) -> bool {
+ self.current + 1 < self.steps.len()
+ && self.steps[self.current].can_continue()
+ }
+}
+
+enum Step {
+ Welcome,
+ Buttons {
+ primary: button::State,
+ secondary: button::State,
+ positive: button::State,
+ },
+ Checkbox {
+ is_checked: bool,
+ },
+ Radio {
+ selection: Option<Language>,
+ },
+ Slider {
+ state: slider::State,
+ value: u16,
+ },
+ Text {
+ size_slider: slider::State,
+ size: u16,
+ color_sliders: [slider::State; 3],
+ color: Color,
+ },
+ RowsAndColumns {
+ layout: Layout,
+ spacing_slider: slider::State,
+ spacing: u16,
+ },
+ End,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum StepMessage {
+ CheckboxToggled(bool),
+ LanguageSelected(Language),
+ SliderChanged(f32),
+ TextSizeChanged(f32),
+ TextColorChanged(Color),
+ LayoutChanged(Layout),
+ SpacingChanged(f32),
+}
+
+impl<'a> Step {
+ fn update(&mut self, msg: StepMessage) {
+ match msg {
+ StepMessage::CheckboxToggled(value) => {
+ if let Step::Checkbox { is_checked } = self {
+ *is_checked = value;
+ }
+ }
+ StepMessage::LanguageSelected(language) => {
+ if let Step::Radio { selection } = self {
+ *selection = Some(language);
+ }
+ }
+ StepMessage::SliderChanged(new_value) => {
+ if let Step::Slider { value, .. } = self {
+ *value = new_value.round() as u16;
+ }
+ }
+ StepMessage::TextSizeChanged(new_size) => {
+ if let Step::Text { size, .. } = self {
+ *size = new_size.round() as u16;
+ }
+ }
+ StepMessage::TextColorChanged(new_color) => {
+ if let Step::Text { color, .. } = self {
+ *color = new_color;
+ }
+ }
+ StepMessage::LayoutChanged(new_layout) => {
+ if let Step::RowsAndColumns { layout, .. } = self {
+ *layout = new_layout;
+ }
+ }
+ StepMessage::SpacingChanged(new_spacing) => {
+ if let Step::RowsAndColumns { spacing, .. } = self {
+ *spacing = new_spacing.round() as u16;
+ }
+ }
+ };
+ }
+
+ fn can_continue(&self) -> bool {
+ match self {
+ Step::Welcome => true,
+ Step::Buttons { .. } => true,
+ Step::Checkbox { is_checked } => *is_checked,
+ Step::Radio { selection } => *selection == Some(Language::Rust),
+ Step::Slider { .. } => true,
+ Step::Text { .. } => true,
+ Step::RowsAndColumns { .. } => true,
+ Step::End => false,
+ }
+ }
+
+ fn layout(&mut self) -> Element<StepMessage> {
+ match self {
+ Step::Welcome => Self::welcome().into(),
+ Step::Buttons {
+ primary,
+ secondary,
+ positive,
+ } => Self::buttons(primary, secondary, positive).into(),
+ Step::Checkbox { is_checked } => Self::checkbox(*is_checked).into(),
+ Step::Radio { selection } => Self::radio(*selection).into(),
+ Step::Slider { state, value } => Self::slider(state, *value).into(),
+ Step::Text {
+ size_slider,
+ size,
+ color_sliders,
+ color,
+ } => Self::text(size_slider, *size, color_sliders, *color).into(),
+ Step::RowsAndColumns {
+ layout,
+ spacing_slider,
+ spacing,
+ } => {
+ Self::rows_and_columns(*layout, spacing_slider, *spacing).into()
+ }
+ Step::End => Self::end().into(),
+ }
+ }
+
+ fn container(title: &str) -> Column<'a, StepMessage> {
+ Column::new()
+ .spacing(20)
+ .align_items(Align::Stretch)
+ .push(Text::new(title).size(50))
+ }
+
+ fn welcome() -> Column<'a, StepMessage> {
+ Self::container("Welcome!")
+ .push(Text::new(
+ "This is a tour that introduces some of the features and \
+ concepts related with UI development in Iced.",
+ ))
+ .push(Text::new(
+ "You will need to interact with the UI in order to reach the \
+ end!",
+ ))
+ }
+
+ fn buttons(
+ primary: &'a mut button::State,
+ secondary: &'a mut button::State,
+ positive: &'a mut button::State,
+ ) -> Column<'a, StepMessage> {
+ Self::container("Button")
+ .push(Text::new("A button can fire actions when clicked."))
+ .push(Text::new(
+ "As of now, there are 3 different types of buttons: \
+ primary, secondary, and positive.",
+ ))
+ .push(Button::new(primary, "Primary"))
+ .push(
+ Button::new(secondary, "Secondary")
+ .class(button::Class::Secondary),
+ )
+ .push(
+ Button::new(positive, "Positive")
+ .class(button::Class::Positive),
+ )
+ .push(Text::new(
+ "Additional types will be added in the near future! Choose \
+ each type smartly depending on the situation.",
+ ))
+ }
+
+ fn checkbox(is_checked: bool) -> Column<'a, StepMessage> {
+ Self::container("Checkbox")
+ .push(Text::new(
+ "A box that can be checked. Useful to build toggle controls.",
+ ))
+ .push(Checkbox::new(
+ is_checked,
+ "Show \"Next\" button",
+ StepMessage::CheckboxToggled,
+ ))
+ .push(Text::new(
+ "A checkbox always has a label, and both the checkbox and its \
+ label can be clicked to toggle it.",
+ ))
+ }
+
+ fn radio(selection: Option<Language>) -> Column<'a, StepMessage> {
+ let question = Column::new()
+ .padding(20)
+ .spacing(10)
+ .push(Text::new("Iced is written in..."))
+ .push(Language::all().iter().cloned().fold(
+ Column::new().padding(10).spacing(20),
+ |choices, language| {
+ choices.push(Radio::new(
+ language,
+ language.into(),
+ selection,
+ StepMessage::LanguageSelected,
+ ))
+ },
+ ));
+
+ Self::container("Radio button")
+ .push(Text::new(
+ "A radio button is normally used to represent a choice. Like \
+ a checkbox, it always has a label.",
+ ))
+ .push(question)
+ }
+
+ fn slider(
+ state: &'a mut slider::State,
+ value: u16,
+ ) -> Column<'a, StepMessage> {
+ Self::container("Slider")
+ .push(Text::new(
+ "A slider allows you to smoothly select a value from a range \
+ of values.",
+ ))
+ .push(Text::new(
+ "The following slider lets you choose an integer from \
+ 0 to 100:",
+ ))
+ .push(Slider::new(
+ state,
+ 0.0..=100.0,
+ value as f32,
+ StepMessage::SliderChanged,
+ ))
+ .push(
+ Text::new(&value.to_string())
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ }
+
+ fn text(
+ size_slider: &'a mut slider::State,
+ size: u16,
+ color_sliders: &'a mut [slider::State; 3],
+ color: Color,
+ ) -> Column<'a, StepMessage> {
+ let size_section = Column::new()
+ .padding(20)
+ .spacing(20)
+ .push(Text::new("You can change its size:"))
+ .push(
+ Text::new(&format!("This text is {} points", size)).size(size),
+ )
+ .push(Slider::new(
+ size_slider,
+ 10.0..=50.0,
+ size as f32,
+ StepMessage::TextSizeChanged,
+ ));
+
+ let [red, green, blue] = color_sliders;
+ let color_section = Column::new()
+ .padding(20)
+ .spacing(20)
+ .push(Text::new("And its color:"))
+ .push(Text::new(&format!("{:?}", color)).color(color))
+ .push(
+ Row::new()
+ .spacing(10)
+ .push(Slider::new(red, 0.0..=1.0, color.r, move |r| {
+ StepMessage::TextColorChanged(Color { r, ..color })
+ }))
+ .push(Slider::new(green, 0.0..=1.0, color.g, move |g| {
+ StepMessage::TextColorChanged(Color { g, ..color })
+ }))
+ .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| {
+ StepMessage::TextColorChanged(Color { b, ..color })
+ })),
+ );
+
+ Self::container("Text")
+ .push(Text::new(
+ "Text is probably the most essential widget for your UI. \
+ It will try to adapt to the dimensions of its container.",
+ ))
+ .push(size_section)
+ .push(color_section)
+ }
+
+ fn rows_and_columns(
+ layout: Layout,
+ spacing_slider: &'a mut slider::State,
+ spacing: u16,
+ ) -> Column<'a, StepMessage> {
+ let row_radio = Radio::new(
+ Layout::Row,
+ "Row",
+ Some(layout),
+ StepMessage::LayoutChanged,
+ );
+
+ let column_radio = Radio::new(
+ Layout::Column,
+ "Column",
+ Some(layout),
+ StepMessage::LayoutChanged,
+ );
+
+ let layout_section: Element<_> = match layout {
+ Layout::Row => Row::new()
+ .spacing(spacing)
+ .push(row_radio)
+ .push(column_radio)
+ .into(),
+ Layout::Column => Column::new()
+ .spacing(spacing)
+ .push(row_radio)
+ .push(column_radio)
+ .into(),
+ };
+
+ let spacing_section = Column::new()
+ .spacing(10)
+ .push(Slider::new(
+ spacing_slider,
+ 0.0..=100.0,
+ spacing as f32,
+ StepMessage::SpacingChanged,
+ ))
+ .push(
+ Text::new(&format!("{} px", spacing))
+ .horizontal_alignment(HorizontalAlignment::Center),
+ );
+
+ Self::container("Rows and columns")
+ .spacing(spacing)
+ .push(Text::new(
+ "Iced uses a layout model based on flexbox to position UI \
+ elements.",
+ ))
+ .push(Text::new(
+ "Rows and columns can be used to distribute content \
+ horizontally or vertically, respectively.",
+ ))
+ .push(layout_section)
+ .push(Text::new(
+ "You can also easily change the spacing between elements:",
+ ))
+ .push(spacing_section)
+ }
+
+ fn end() -> Column<'a, StepMessage> {
+ Self::container("You reached the end!")
+ .push(Text::new(
+ "This tour will be extended as more features are added.",
+ ))
+ .push(Text::new("Make sure to keep an eye on it!"))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Language {
+ Rust,
+ Elm,
+ Ruby,
+ Haskell,
+ C,
+ Other,
+}
+
+impl Language {
+ fn all() -> [Language; 6] {
+ [
+ Language::C,
+ Language::Elm,
+ Language::Ruby,
+ Language::Haskell,
+ Language::Rust,
+ Language::Other,
+ ]
+ }
+}
+
+impl From<Language> for &str {
+ fn from(language: Language) -> &'static str {
+ match language {
+ Language::Rust => "Rust",
+ Language::Elm => "Elm",
+ Language::Ruby => "Ruby",
+ Language::Haskell => "Haskell",
+ Language::C => "C",
+ Language::Other => "Other",
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Layout {
+ Row,
+ Column,
+}