summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2020-03-20 12:10:52 +0100
committerLibravatar GitHub <noreply@github.com>2020-03-20 12:10:52 +0100
commitf7ec679fec1b69c6dc5bc12d60627629f086bf22 (patch)
tree58ec4dd11e50f11ef85b972fee211930e7a0e7c0 /examples
parent93f5640a2dc06d8f1bf2b0d033d45c62f8985380 (diff)
parentfb744a338c1b7566a3db9a3d24c03729b4858217 (diff)
downloadiced-f7ec679fec1b69c6dc5bc12d60627629f086bf22.tar.gz
iced-f7ec679fec1b69c6dc5bc12d60627629f086bf22.tar.bz2
iced-f7ec679fec1b69c6dc5bc12d60627629f086bf22.zip
Merge pull request #224 from hecrj/feature/panes-widget
Pane grid widget
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md1
-rw-r--r--examples/pane_grid/Cargo.toml9
-rw-r--r--examples/pane_grid/README.md28
-rw-r--r--examples/pane_grid/src/main.rs306
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
+ }
+ }
+ }
+}