diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/README.md | 1 | ||||
-rw-r--r-- | examples/pane_grid/Cargo.toml | 9 | ||||
-rw-r--r-- | examples/pane_grid/README.md | 28 | ||||
-rw-r--r-- | examples/pane_grid/src/main.rs | 306 |
4 files changed, 344 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md index 04399b93..a7673705 100644 --- a/examples/README.md +++ b/examples/README.md @@ -76,6 +76,7 @@ A bunch of simpler examples exist: - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). - [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. +- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. - [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. - [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms. diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml new file mode 100644 index 00000000..3ed912ac --- /dev/null +++ b/examples/pane_grid/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pane_grid" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md new file mode 100644 index 00000000..3653fc5b --- /dev/null +++ b/examples/pane_grid/README.md @@ -0,0 +1,28 @@ +## Pane grid + +A grid of panes that can be split, resized, and reorganized. + +This example showcases the `PaneGrid` widget, which features: + +* Vertical and horizontal splits +* Tracking of the last active pane +* Mouse-based resizing +* Drag and drop to reorganize panes +* Hotkey support +* Configurable modifier keys +* API to perform actions programmatically (`split`, `swap`, `resize`, etc.) + +The __[`main`]__ file contains all the code of the example. + +<div align="center"> + <a href="https://gfycat.com/mixedflatjellyfish"> + <img src="https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif"> + </a> +</div> + +You can run it with `cargo run`: +``` +cargo run --package pane_grid +``` + +[`main`]: src/main.rs diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs new file mode 100644 index 00000000..dafc396c --- /dev/null +++ b/examples/pane_grid/src/main.rs @@ -0,0 +1,306 @@ +use iced::{ + button, keyboard, pane_grid, scrollable, Align, Button, Column, Container, + Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, + Settings, Text, +}; + +pub fn main() { + Example::run(Settings::default()) +} + +struct Example { + panes: pane_grid::State<Content>, + panes_created: usize, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Split(pane_grid::Axis, pane_grid::Pane), + SplitFocused(pane_grid::Axis), + FocusAdjacent(pane_grid::Direction), + Dragged(pane_grid::DragEvent), + Resized(pane_grid::ResizeEvent), + Close(pane_grid::Pane), + CloseFocused, +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + let (panes, _) = pane_grid::State::new(Content::new(0)); + + Example { + panes, + panes_created: 1, + } + } + + fn title(&self) -> String { + String::from("Pane grid - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::Split(axis, pane) => { + let _ = self.panes.split( + axis, + &pane, + Content::new(self.panes_created), + ); + + self.panes_created += 1; + } + Message::SplitFocused(axis) => { + if let Some(pane) = self.panes.active() { + let _ = self.panes.split( + axis, + &pane, + Content::new(self.panes_created), + ); + + self.panes_created += 1; + } + } + Message::FocusAdjacent(direction) => { + if let Some(pane) = self.panes.active() { + if let Some(adjacent) = + self.panes.adjacent(&pane, direction) + { + self.panes.focus(&adjacent); + } + } + } + Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { + self.panes.resize(&split, ratio); + } + Message::Dragged(pane_grid::DragEvent::Dropped { + pane, + target, + }) => { + self.panes.swap(&pane, &target); + } + Message::Dragged(_) => {} + Message::Close(pane) => { + let _ = self.panes.close(&pane); + } + Message::CloseFocused => { + if let Some(pane) = self.panes.active() { + let _ = self.panes.close(&pane); + } + } + } + } + + fn view(&mut self) -> Element<Message> { + let total_panes = self.panes.len(); + + let pane_grid = + PaneGrid::new(&mut self.panes, |pane, content, focus| { + content.view(pane, focus, total_panes) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_drag(Message::Dragged) + .on_resize(Message::Resized) + .on_key_press(handle_hotkey); + + Column::new() + .width(Length::Fill) + .height(Length::Fill) + .padding(10) + .push(pane_grid) + .into() + } +} + +fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> { + use keyboard::KeyCode; + use pane_grid::{Axis, Direction}; + + let direction = match event.key_code { + KeyCode::Up => Some(Direction::Up), + KeyCode::Down => Some(Direction::Down), + KeyCode::Left => Some(Direction::Left), + KeyCode::Right => Some(Direction::Right), + _ => None, + }; + + match event.key_code { + KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), + KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), + KeyCode::W => Some(Message::CloseFocused), + _ => direction.map(Message::FocusAdjacent), + } +} + +struct Content { + id: usize, + scroll: scrollable::State, + split_horizontally: button::State, + split_vertically: button::State, + close: button::State, +} + +impl Content { + fn new(id: usize) -> Self { + Content { + id, + scroll: scrollable::State::new(), + split_horizontally: button::State::new(), + split_vertically: button::State::new(), + close: button::State::new(), + } + } + fn view( + &mut self, + pane: pane_grid::Pane, + focus: Option<pane_grid::Focus>, + total_panes: usize, + ) -> Element<Message> { + let Content { + id, + scroll, + split_horizontally, + split_vertically, + close, + } = self; + + let button = |state, label, message, style| { + Button::new( + state, + Text::new(label) + .width(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center) + .size(16), + ) + .width(Length::Fill) + .padding(8) + .on_press(message) + .style(style) + }; + + let mut controls = Column::new() + .spacing(5) + .max_width(150) + .push(button( + split_horizontally, + "Split horizontally", + Message::Split(pane_grid::Axis::Horizontal, pane), + style::Button::Primary, + )) + .push(button( + split_vertically, + "Split vertically", + Message::Split(pane_grid::Axis::Vertical, pane), + style::Button::Primary, + )); + + if total_panes > 1 { + controls = controls.push(button( + close, + "Close", + Message::Close(pane), + style::Button::Destructive, + )); + } + + let content = Scrollable::new(scroll) + .width(Length::Fill) + .spacing(10) + .align_items(Align::Center) + .push(Text::new(format!("Pane {}", id)).size(30)) + .push(controls); + + Container::new(Column::new().padding(5).push(content)) + .width(Length::Fill) + .height(Length::Fill) + .center_y() + .style(style::Pane { + is_focused: focus.is_some(), + }) + .into() + } +} + +mod style { + use iced::{button, container, Background, Color, Vector}; + + const SURFACE: Color = Color::from_rgb( + 0xF2 as f32 / 255.0, + 0xF3 as f32 / 255.0, + 0xF5 as f32 / 255.0, + ); + + const ACTIVE: Color = Color::from_rgb( + 0x72 as f32 / 255.0, + 0x89 as f32 / 255.0, + 0xDA as f32 / 255.0, + ); + + const HOVERED: Color = Color::from_rgb( + 0x67 as f32 / 255.0, + 0x7B as f32 / 255.0, + 0xC4 as f32 / 255.0, + ); + + pub struct Pane { + pub is_focused: bool, + } + + impl container::StyleSheet for Pane { + fn style(&self) -> container::Style { + container::Style { + background: Some(Background::Color(SURFACE)), + border_width: 2, + border_color: Color { + a: if self.is_focused { 1.0 } else { 0.3 }, + ..Color::BLACK + }, + ..Default::default() + } + } + } + + pub enum Button { + Primary, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + let (background, text_color) = match self { + Button::Primary => (Some(ACTIVE), Color::WHITE), + Button::Destructive => { + (None, Color::from_rgb8(0xFF, 0x47, 0x47)) + } + }; + + button::Style { + text_color, + background: background.map(Background::Color), + border_radius: 5, + shadow_offset: Vector::new(0.0, 0.0), + ..button::Style::default() + } + } + + fn hovered(&self) -> button::Style { + let active = self.active(); + + let background = match self { + Button::Primary => Some(HOVERED), + Button::Destructive => Some(Color { + a: 0.2, + ..active.text_color + }), + }; + + button::Style { + background: background.map(Background::Color), + ..active + } + } + } +} |