use iced::{ button, executor, keyboard, pane_grid, scrollable, Align, Application, Button, Column, Command, Container, Element, HorizontalAlignment, Length, PaneGrid, Scrollable, Settings, Subscription, Text, }; use iced_native::{subscription, Event}; pub fn main() -> iced::Result { Example::run(Settings::default()) } struct Example { panes: pane_grid::State, panes_created: usize, focus: Option, } #[derive(Debug, Clone, Copy)] enum Message { Split(pane_grid::Axis, pane_grid::Pane), SplitFocused(pane_grid::Axis), FocusAdjacent(pane_grid::Direction), Clicked(pane_grid::Pane), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), Close(pane_grid::Pane), CloseFocused, } impl Application for Example { type Message = Message; type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Self, Command) { let (panes, _) = pane_grid::State::new(Content::new(0)); ( Example { panes, panes_created: 1, focus: None, }, Command::none(), ) } fn title(&self) -> String { String::from("Pane grid - Iced") } fn update(&mut self, message: Message) -> Command { match message { Message::Split(axis, pane) => { let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); if let Some((pane, _)) = result { self.focus = Some(pane); } self.panes_created += 1; } Message::SplitFocused(axis) => { if let Some(pane) = self.focus { let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); if let Some((pane, _)) = result { self.focus = Some(pane); } self.panes_created += 1; } } Message::FocusAdjacent(direction) => { if let Some(pane) = self.focus { if let Some(adjacent) = self.panes.adjacent(&pane, direction) { self.focus = Some(adjacent); } } } Message::Clicked(pane) => { self.focus = Some(pane); } 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) => { if let Some((_, sibling)) = self.panes.close(&pane) { self.focus = Some(sibling); } } Message::CloseFocused => { if let Some(pane) = self.focus { if let Some((_, sibling)) = self.panes.close(&pane) { self.focus = Some(sibling); } } } } Command::none() } fn subscription(&self) -> Subscription { subscription::events_with(|event| match event { Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key_code, }) if modifiers.is_command_pressed() => handle_hotkey(key_code), _ => None, }) } fn view(&mut self) -> Element { let focus = self.focus; let total_panes = self.panes.len(); let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| { let is_focused = focus == Some(pane); let title_bar = pane_grid::TitleBar::new(format!("Pane {}", content.id)) .padding(10) .style(style::TitleBar { is_focused }); pane_grid::Content::new(content.view(pane, total_panes)) .title_bar(title_bar) .style(style::Pane { is_focused }) }) .width(Length::Fill) .height(Length::Fill) .spacing(10) .on_click(Message::Clicked) .on_drag(Message::Dragged) .on_resize(10, Message::Resized); Container::new(pane_grid) .width(Length::Fill) .height(Length::Fill) .padding(10) .into() } } fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; let direction = match key_code { KeyCode::Up => Some(Direction::Up), KeyCode::Down => Some(Direction::Down), KeyCode::Left => Some(Direction::Left), KeyCode::Right => Some(Direction::Right), _ => None, }; match 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, total_panes: usize, ) -> Element { let Content { 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(controls); Container::new(content) .width(Length::Fill) .height(Length::Fill) .padding(5) .center_y() .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 TitleBar { pub is_focused: bool, } impl container::StyleSheet for TitleBar { fn style(&self) -> container::Style { let pane = Pane { is_focused: self.is_focused, } .style(); container::Style { text_color: Some(Color::WHITE), background: Some(pane.border_color.into()), ..Default::default() } } } 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: if self.is_focused { Color::BLACK } else { Color::from_rgb(0.7, 0.7, 0.7) }, ..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 } } } }