use iced::alignment::{self, Alignment}; use iced::keyboard; use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::{ button, column, container, responsive, row, scrollable, text, }; use iced::{Color, Element, Length, Size, Subscription}; pub fn main() -> iced::Result { iced::application("Pane Grid - Iced", Example::update, Example::view) .subscription(Example::subscription) .run() } 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), TogglePin(pane_grid::Pane), Maximize(pane_grid::Pane), Restore, Close(pane_grid::Pane), CloseFocused, } impl Example { fn new() -> Self { let (panes, _) = pane_grid::State::new(Pane::new(0)); Example { panes, panes_created: 1, focus: None, } } fn update(&mut self, message: Message) { match message { Message::Split(axis, pane) => { let result = self.panes.split(axis, pane, Pane::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, Pane::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.drop(pane, target); } Message::Dragged(_) => {} Message::TogglePin(pane) => { if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(pane) { *is_pinned = !*is_pinned; } } Message::Maximize(pane) => self.panes.maximize(pane), Message::Restore => { self.panes.restore(); } 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(Pane { is_pinned, .. }) = self.panes.get(pane) { if !is_pinned { if let Some((_, sibling)) = self.panes.close(pane) { self.focus = Some(sibling); } } } } } } } fn subscription(&self) -> Subscription { keyboard::on_key_press(|key_code, modifiers| { if !modifiers.command() { return None; } handle_hotkey(key_code) }) } fn view(&self) -> Element { let focus = self.focus; let total_panes = self.panes.len(); let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| { let is_focused = focus == Some(id); let pin_button = button( text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), ) .on_press(Message::TogglePin(id)) .padding(3); let title = row![ pin_button, "Pane", text(pane.id.to_string()).color(if is_focused { PANE_ID_COLOR_FOCUSED } else { PANE_ID_COLOR_UNFOCUSED }), ] .spacing(5); let title_bar = pane_grid::TitleBar::new(title) .controls(view_controls( id, total_panes, pane.is_pinned, is_maximized, )) .padding(10) .style(if is_focused { style::title_bar_focused } else { style::title_bar_active }); pane_grid::Content::new(responsive(move |size| { view_content(id, total_panes, pane.is_pinned, size) })) .title_bar(title_bar) .style(if is_focused { style::pane_focused } else { style::pane_active }) }) .width(Length::Fill) .height(Length::Fill) .spacing(10) .on_click(Message::Clicked) .on_drag(Message::Dragged) .on_resize(10, Message::Resized); container(pane_grid) .width(Length::Fill) .height(Length::Fill) .padding(10) .into() } } impl Default for Example { fn default() -> Self { Example::new() } } const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb( 0xFF as f32 / 255.0, 0xC7 as f32 / 255.0, 0xC7 as f32 / 255.0, ); const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb( 0xFF as f32 / 255.0, 0x47 as f32 / 255.0, 0x47 as f32 / 255.0, ); fn handle_hotkey(key: keyboard::Key) -> Option { use keyboard::key::{self, Key}; use pane_grid::{Axis, Direction}; match key.as_ref() { Key::Character("v") => Some(Message::SplitFocused(Axis::Vertical)), Key::Character("h") => Some(Message::SplitFocused(Axis::Horizontal)), Key::Character("w") => Some(Message::CloseFocused), Key::Named(key) => { let direction = match key { key::Named::ArrowUp => Some(Direction::Up), key::Named::ArrowDown => Some(Direction::Down), key::Named::ArrowLeft => Some(Direction::Left), key::Named::ArrowRight => Some(Direction::Right), _ => None, }; direction.map(Message::FocusAdjacent) } _ => None, } } #[derive(Clone, Copy)] struct Pane { id: usize, pub is_pinned: bool, } impl Pane { fn new(id: usize) -> Self { Self { id, is_pinned: false, } } } fn view_content<'a>( pane: pane_grid::Pane, total_panes: usize, is_pinned: bool, size: Size, ) -> Element<'a, Message> { let button = |label, message| { button( text(label) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center) .size(16), ) .width(Length::Fill) .padding(8) .on_press(message) }; let controls = column![ button( "Split horizontally", Message::Split(pane_grid::Axis::Horizontal, pane), ), button( "Split vertically", Message::Split(pane_grid::Axis::Vertical, pane), ) ] .push_maybe(if total_panes > 1 && !is_pinned { Some(button("Close", Message::Close(pane)).style(button::danger)) } else { None }) .spacing(5) .max_width(160); let content = column![text!("{}x{}", size.width, size.height).size(24), controls,] .spacing(10) .align_items(Alignment::Center); container(scrollable(content)) .center_y(Length::Fill) .padding(5) .into() } fn view_controls<'a>( pane: pane_grid::Pane, total_panes: usize, is_pinned: bool, is_maximized: bool, ) -> Element<'a, Message> { let row = row![].spacing(5).push_maybe(if total_panes > 1 { let (content, message) = if is_maximized { ("Restore", Message::Restore) } else { ("Maximize", Message::Maximize(pane)) }; Some( button(text(content).size(14)) .style(button::secondary) .padding(3) .on_press(message), ) } else { None }); let close = button(text("Close").size(14)) .style(button::danger) .padding(3) .on_press_maybe(if total_panes > 1 && !is_pinned { Some(Message::Close(pane)) } else { None }); row.push(close).into() } mod style { use iced::widget::container; use iced::{Border, Theme}; pub fn title_bar_active(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); container::Style { text_color: Some(palette.background.strong.text), background: Some(palette.background.strong.color.into()), ..Default::default() } } pub fn title_bar_focused(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); container::Style { text_color: Some(palette.primary.strong.text), background: Some(palette.primary.strong.color.into()), ..Default::default() } } pub fn pane_active(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); container::Style { background: Some(palette.background.weak.color.into()), border: Border { width: 2.0, color: palette.background.strong.color, ..Border::default() }, ..Default::default() } } pub fn pane_focused(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); container::Style { background: Some(palette.background.weak.color.into()), border: Border { width: 2.0, color: palette.primary.strong.color, ..Border::default() }, ..Default::default() } } }