diff options
| author | 2023-03-13 18:19:16 -0700 | |
|---|---|---|
| committer | 2023-03-14 11:05:41 -0700 | |
| commit | ce4b2c93d9802dfb8cd3fc9033d76651d4bbc75b (patch) | |
| tree | a690bb94262b7a898a608d11e18960db81e7ef0c /examples/multi_window | |
| parent | 8ba18430800142965549077373e2a45d0a3429a1 (diff) | |
| download | iced-ce4b2c93d9802dfb8cd3fc9033d76651d4bbc75b.tar.gz iced-ce4b2c93d9802dfb8cd3fc9033d76651d4bbc75b.tar.bz2 iced-ce4b2c93d9802dfb8cd3fc9033d76651d4bbc75b.zip | |
Added simpler MW example
Diffstat (limited to '')
| -rw-r--r-- | examples/multi_window/Cargo.toml | 8 | ||||
| -rw-r--r-- | examples/multi_window/src/main.rs | 654 | ||||
| -rw-r--r-- | examples/multi_window_panes/Cargo.toml | 12 | ||||
| -rw-r--r-- | examples/multi_window_panes/src/main.rs | 624 | 
4 files changed, 739 insertions, 559 deletions
| diff --git a/examples/multi_window/Cargo.toml b/examples/multi_window/Cargo.toml index a59a0e68..0cb5d546 100644 --- a/examples/multi_window/Cargo.toml +++ b/examples/multi_window/Cargo.toml @@ -1,13 +1,9 @@  [package]  name = "multi_window"  version = "0.1.0" -authors = ["Richard Custodio <richardsoncusto@gmail.com>"] +authors = ["Bingus <shankern@protonmail.com>"]  edition = "2021"  publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html  [dependencies] -iced = { path = "../..", features = ["debug", "multi-window", "tokio"] } -env_logger = "0.10.0" -iced_native = { path = "../../native" } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "multi-window"] }
\ No newline at end of file diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs index 60f32a7d..5699ece0 100644 --- a/examples/multi_window/src/main.rs +++ b/examples/multi_window/src/main.rs @@ -1,90 +1,50 @@ -use iced::alignment::{self, Alignment}; -use iced::{executor, time}; -use iced::keyboard; -use iced::multi_window::Application; -use iced::theme::{self, Theme}; -use iced::widget::pane_grid::{self, PaneGrid}; -use iced::widget::{ -    button, column, container, pick_list, row, scrollable, text, text_input, +use iced::multi_window::{self, Application}; +use iced::widget::{button, column, container, scrollable, text, text_input}; +use iced::{ +    executor, window, Alignment, Command, Element, Length, Settings, Theme,  }; -use iced::window; -use iced::{Color, Command, Element, Length, Settings, Size, Subscription}; -use iced_lazy::responsive; -use iced_native::{event, subscription, Event}; - -use iced_native::widget::scrollable::{Properties, RelativeOffset}; -use iced_native::window::Id;  use std::collections::HashMap; -use std::time::{Duration, Instant}; - -pub fn main() -> iced::Result { -    env_logger::init(); +fn main() -> iced::Result {      Example::run(Settings::default())  } +#[derive(Default)]  struct Example { +    windows_count: usize,      windows: HashMap<window::Id, Window>, -    panes_created: usize, -    count: usize, -    _focused: window::Id,  } -#[derive(Debug)]  struct Window { +    id: window::Id,      title: String, -    scale: f64, -    panes: pane_grid::State<Pane>, -    focus: Option<pane_grid::Pane>, +    scale_input: String, +    current_scale: f64,  }  #[derive(Debug, Clone)]  enum Message { -    Window(window::Id, WindowMessage), -    CountIncremented(Instant), -} - -#[derive(Debug, Clone)] -enum WindowMessage { -    Split(pane_grid::Axis, pane_grid::Pane), -    SplitFocused(pane_grid::Axis), -    FocusAdjacent(pane_grid::Direction), -    Clicked(pane_grid::Pane), -    Dragged(pane_grid::DragEvent), -    PopOut(pane_grid::Pane), -    Resized(pane_grid::ResizeEvent), -    TitleChanged(String), -    ToggleMoving(pane_grid::Pane), -    TogglePin(pane_grid::Pane), -    Close(pane_grid::Pane), -    CloseFocused, -    SelectedWindow(pane_grid::Pane, SelectableWindow), -    CloseWindow, -    SnapToggle, +    ScaleInputChanged(window::Id, String), +    ScaleChanged(window::Id, String), +    TitleChanged(window::Id, String), +    CloseWindow(window::Id), +    NewWindow,  } -impl Application for Example { +impl multi_window::Application for Example {      type Executor = executor::Default;      type Message = Message;      type Theme = Theme;      type Flags = ();      fn new(_flags: ()) -> (Self, Command<Message>) { -        let (panes, _) = -            pane_grid::State::new(Pane::new(0, pane_grid::Axis::Horizontal)); -        let window = Window { -            panes, -            focus: None, -            title: String::from("Default window"), -            scale: 1.0, -        }; -          (              Example { -                windows: HashMap::from([(window::Id::MAIN, window)]), -                panes_created: 1, -                count: 0, -                _focused: window::Id::MAIN, +                windows_count: 0, +                windows: HashMap::from([( +                    window::Id::MAIN, +                    Window::new(window::Id::MAIN), +                )]),              },              Command::none(),          ) @@ -93,530 +53,118 @@ impl Application for Example {      fn title(&self, window: window::Id) -> String {          self.windows              .get(&window) -            .map(|w| w.title.clone()) -            .unwrap_or(String::from("New Window")) +            .map(|window| window.title.clone()) +            .unwrap_or("Example".to_string())      }      fn update(&mut self, message: Message) -> Command<Message> {          match message { -            Message::Window(id, message) => match message { -                WindowMessage::SnapToggle => { -                    let window = self.windows.get_mut(&id).unwrap(); - -                    if let Some(focused) = &window.focus { -                        let pane = window.panes.get_mut(focused).unwrap(); - -                        let cmd = scrollable::snap_to( -                            pane.scrollable_id.clone(), -                            if pane.snapped { -                                RelativeOffset::START -                            } else { -                                RelativeOffset::END -                            }, -                        ); - -                        pane.snapped = !pane.snapped; -                        return cmd; -                    } -                } -                WindowMessage::Split(axis, pane) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    let result = window.panes.split( -                        axis, -                        &pane, -                        Pane::new(self.panes_created, axis), -                    ); - -                    if let Some((pane, _)) = result { -                        window.focus = Some(pane); -                    } - -                    self.panes_created += 1; -                } -                WindowMessage::SplitFocused(axis) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    if let Some(pane) = window.focus { -                        let result = window.panes.split( -                            axis, -                            &pane, -                            Pane::new(self.panes_created, axis), -                        ); - -                        if let Some((pane, _)) = result { -                            window.focus = Some(pane); -                        } - -                        self.panes_created += 1; -                    } -                } -                WindowMessage::FocusAdjacent(direction) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    if let Some(pane) = window.focus { -                        if let Some(adjacent) = -                            window.panes.adjacent(&pane, direction) -                        { -                            window.focus = Some(adjacent); -                        } -                    } -                } -                WindowMessage::Clicked(pane) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    window.focus = Some(pane); -                } -                WindowMessage::CloseWindow => { -                    let _ = self.windows.remove(&id); -                    return window::close(id); -                } -                WindowMessage::Resized(pane_grid::ResizeEvent { split, ratio }) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    window.panes.resize(&split, ratio); -                } -                WindowMessage::SelectedWindow(pane, selected) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    let (mut pane, _) = window.panes.close(&pane).unwrap(); -                    pane.is_moving = false; - -                    if let Some(window) = self.windows.get_mut(&selected.0) { -                        let (&first_pane, _) = window.panes.iter().next().unwrap(); -                        let result = -                            window.panes.split(pane.axis, &first_pane, pane); - -                        if let Some((pane, _)) = result { -                            window.focus = Some(pane); -                        } -                    } -                } -                WindowMessage::ToggleMoving(pane) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    if let Some(pane) = window.panes.get_mut(&pane) { -                        pane.is_moving = !pane.is_moving; -                    } -                } -                WindowMessage::TitleChanged(title) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    window.title = title; -                } -                WindowMessage::PopOut(pane) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    if let Some((popped, sibling)) = window.panes.close(&pane) { -                        window.focus = Some(sibling); +            Message::ScaleInputChanged(id, scale) => { +                let window = +                    self.windows.get_mut(&id).expect("Window not found!"); +                window.scale_input = scale; +            } +            Message::ScaleChanged(id, scale) => { +                let window = +                    self.windows.get_mut(&id).expect("Window not found!"); + +                window.current_scale = scale +                    .parse::<f64>() +                    .unwrap_or(window.current_scale) +                    .clamp(0.5, 5.0); +            } +            Message::TitleChanged(id, title) => { +                let window = +                    self.windows.get_mut(&id).expect("Window not found."); -                        let (panes, _) = pane_grid::State::new(popped); -                        let window = Window { -                            panes, -                            focus: None, -                            title: format!("New window ({})", self.windows.len()), -                            scale: 1.0 + (self.windows.len() as f64 / 10.0), -                        }; +                window.title = title; +            } +            Message::CloseWindow(id) => { +                return window::close(id); +            } +            Message::NewWindow => { +                self.windows_count += 1; +                let id = window::Id::new(self.windows_count); +                self.windows.insert(id, Window::new(id)); -                        let window_id = window::Id::new(self.windows.len()); -                        self.windows.insert(window_id, window); -                        return window::spawn(window_id, Default::default()); -                    } -                } -                WindowMessage::Dragged(pane_grid::DragEvent::Dropped { -                                           pane, -                                           target, -                                       }) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    window.panes.swap(&pane, &target); -                } -                // WindowMessage::Dragged(pane_grid::DragEvent::Picked { pane }) => { -                //     println!("Picked {pane:?}"); -                // } -                WindowMessage::Dragged(_) => {} -                WindowMessage::TogglePin(pane) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    if let Some(Pane { is_pinned, .. }) = -                        window.panes.get_mut(&pane) -                    { -                        *is_pinned = !*is_pinned; -                    } -                } -                WindowMessage::Close(pane) => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    if let Some((_, sibling)) = window.panes.close(&pane) { -                        window.focus = Some(sibling); -                    } -                } -                WindowMessage::CloseFocused => { -                    let window = self.windows.get_mut(&id).unwrap(); -                    if let Some(pane) = window.focus { -                        if let Some(Pane { is_pinned, .. }) = -                            window.panes.get(&pane) -                        { -                            if !is_pinned { -                                if let Some((_, sibling)) = -                                    window.panes.close(&pane) -                                { -                                    window.focus = Some(sibling); -                                } -                            } -                        } -                    } -                } -            }, -            Message::CountIncremented(_) => { -                self.count += 1; +                return window::spawn(id, window::Settings::default());              }          }          Command::none()      } -    fn subscription(&self) -> Subscription<Message> { -        Subscription::batch(vec![ -            subscription::events_with(|event, status| { -                if let event::Status::Captured = status { -                    return None; -                } - -                match event { -                    Event::Keyboard(keyboard::Event::KeyPressed { -                                        modifiers, -                                        key_code, -                                    }) if modifiers.command() => { -                        handle_hotkey(key_code).map(|message| { -                            Message::Window(window::Id::new(0usize), message) -                        }) -                    } // TODO(derezzedex) -                    _ => None, -                } -            }), -            time::every(Duration::from_secs(1)).map(Message::CountIncremented), -        ]) -    } - -    fn view(&self, window_id: window::Id) -> Element<Message> { -        if let Some(window) = self.windows.get(&window_id) { -            let focus = window.focus; -            let total_panes = window.panes.len(); - -            let window_controls = row![ -                text_input( -                    "Window title", -                    &window.title, -                    WindowMessage::TitleChanged, -                ), -                button(text("Close")) -                    .on_press(WindowMessage::CloseWindow) -                    .style(theme::Button::Destructive), -            ] -            .spacing(5) -            .align_items(Alignment::Center); - -            let pane_grid = PaneGrid::new(&window.panes, |id, pane, _| { -                let is_focused = focus == Some(id); - -                let pin_button = button( -                    text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), -                ) -                .on_press(WindowMessage::TogglePin(id)) -                .padding(3); - -                let title = row![ -                    pin_button, -                    "Pane", -                    text(pane.id.to_string()).style(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, -                        pane.is_moving, -                        &window.title, -                        window_id, -                        &self.windows, -                    )) -                    .padding(10) -                    .style(if is_focused { -                        style::title_bar_focused -                    } else { -                        style::title_bar_active -                    }); +    fn view(&self, window: window::Id) -> Element<Message> { +        let window = self +            .windows +            .get(&window) +            .map(|window| window.view()) +            .unwrap(); -                pane_grid::Content::new(responsive(move |size| { -                    view_content( -                        id, -                        pane.scrollable_id.clone(), -                        self.count, -                        total_panes, -                        pane.is_pinned, -                        size, -                    ) -                })) -                .title_bar(title_bar) -                .style(if is_focused { -                    style::pane_focused -                } else { -                    style::pane_active -                }) -            }) +        container(window)              .width(Length::Fill)              .height(Length::Fill) -            .spacing(10) -            .on_click(WindowMessage::Clicked) -            .on_drag(WindowMessage::Dragged) -            .on_resize(10, WindowMessage::Resized); - -            let content: Element<_> = column![window_controls, pane_grid] -                .width(Length::Fill) -                .height(Length::Fill) -                .padding(10) -                .into(); - -            return content -                .map(move |message| Message::Window(window_id, message)); -        } - -        container(text("This shouldn't be possible!").size(20))              .center_x()              .center_y()              .into()      } -    fn close_requested(&self, window: window::Id) -> Self::Message { -        Message::Window(window, WindowMessage::CloseWindow) -    } - -    fn scale_factor(&self, window: Id) -> f64 { -        self.windows.get(&window).map(|w| w.scale).unwrap_or(1.0) -    } -} - -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_code: keyboard::KeyCode) -> Option<WindowMessage> { -    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(WindowMessage::SplitFocused(Axis::Vertical)), -        KeyCode::H => Some(WindowMessage::SplitFocused(Axis::Horizontal)), -        KeyCode::W => Some(WindowMessage::CloseFocused), -        _ => direction.map(WindowMessage::FocusAdjacent), -    } -} - -#[derive(Debug, Clone)] -struct SelectableWindow(window::Id, String); - -impl PartialEq for SelectableWindow { -    fn eq(&self, other: &Self) -> bool { -        self.0 == other.0 +    fn scale_factor(&self, window: window::Id) -> f64 { +        self.windows +            .get(&window) +            .map(|window| window.current_scale) +            .unwrap_or(1.0)      } -} - -impl Eq for SelectableWindow {} -impl std::fmt::Display for SelectableWindow { -    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -        self.1.fmt(f) +    fn close_requested(&self, window: window::Id) -> Self::Message { +        Message::CloseWindow(window)      }  } -#[derive(Debug)] -struct Pane { -    id: usize, -    pub scrollable_id: scrollable::Id, -    pub axis: pane_grid::Axis, -    pub is_pinned: bool, -    pub is_moving: bool, -    pub snapped: bool, -} - -impl Pane { -    fn new(id: usize, axis: pane_grid::Axis) -> Self { +impl Window { +    fn new(id: window::Id) -> Self {          Self {              id, -            scrollable_id: scrollable::Id::unique(), -            axis, -            is_pinned: false, -            is_moving: false, -            snapped: false, +            title: "Window".to_string(), +            scale_input: "1.0".to_string(), +            current_scale: 1.0,          }      } -} - -fn view_content<'a>( -    pane: pane_grid::Pane, -    scrollable_id: scrollable::Id, -    count: usize, -    total_panes: usize, -    is_pinned: bool, -    size: Size, -) -> Element<'a, WindowMessage> { -    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 mut controls = column![ -        button( -            "Split horizontally", -            WindowMessage::Split(pane_grid::Axis::Horizontal, pane), -        ), -        button( -            "Split vertically", -            WindowMessage::Split(pane_grid::Axis::Vertical, pane), -        ), -        button("Snap", WindowMessage::SnapToggle,) -    ] -    .spacing(5) -    .max_width(150); - -    if total_panes > 1 && !is_pinned { -        controls = controls.push( -            button("Close", WindowMessage::Close(pane)) -                .style(theme::Button::Destructive), -        ); +    fn view(&self) -> Element<Message> { +        window_view(self.id, &self.scale_input, &self.title)      } - -    let content = column![ -        text(format!("{}x{}", size.width, size.height)).size(24), -        controls, -        text(format!("{count}")).size(48), -    ] -    .width(Length::Fill) -    .height(800) -    .spacing(10) -    .align_items(Alignment::Center); - -    container( -        scrollable(content) -            .height(Length::Fill) -            .vertical_scroll(Properties::new()) -            .id(scrollable_id), -    ) -    .width(Length::Fill) -    .height(Length::Fill) -    .padding(5) -    .center_y() -    .into()  } -fn view_controls<'a>( -    pane: pane_grid::Pane, -    total_panes: usize, -    is_pinned: bool, -    is_moving: bool, -    window_title: &'a str, -    window_id: window::Id, -    windows: &HashMap<window::Id, Window>, -) -> Element<'a, WindowMessage> { -    let window_selector = { -        let options: Vec<_> = windows -            .iter() -            .map(|(id, window)| SelectableWindow(*id, window.title.clone())) -            .collect(); -        pick_list( -            options, -            Some(SelectableWindow(window_id, window_title.to_string())), -            move |window| WindowMessage::SelectedWindow(pane, window), -        ) -    }; - -    let mut move_to = button(text("Move to").size(14)).padding(3); - -    let mut pop_out = button(text("Pop Out").size(14)).padding(3); - -    let mut close = button(text("Close").size(14)) -        .style(theme::Button::Destructive) -        .padding(3); - -    if total_panes > 1 && !is_pinned { -        close = close.on_press(WindowMessage::Close(pane)); -        pop_out = pop_out.on_press(WindowMessage::PopOut(pane)); -    } - -    if windows.len() > 1 && total_panes > 1 && !is_pinned { -        move_to = move_to.on_press(WindowMessage::ToggleMoving(pane)); -    } - -    let mut content = row![].spacing(10); -    if is_moving { -        content = content.push(pop_out).push(window_selector).push(close); -    } else { -        content = content.push(pop_out).push(move_to).push(close); -    } - -    content.into() -} - -mod style { -    use iced::widget::container; -    use iced::Theme; - -    pub fn title_bar_active(theme: &Theme) -> container::Appearance { -        let palette = theme.extended_palette(); - -        container::Appearance { -            text_color: Some(palette.background.strong.text), -            background: Some(palette.background.strong.color.into()), -            ..Default::default() -        } -    } - -    pub fn title_bar_focused(theme: &Theme) -> container::Appearance { -        let palette = theme.extended_palette(); - -        container::Appearance { -            text_color: Some(palette.primary.strong.text), -            background: Some(palette.primary.strong.color.into()), -            ..Default::default() -        } -    } - -    pub fn pane_active(theme: &Theme) -> container::Appearance { -        let palette = theme.extended_palette(); - -        container::Appearance { -            background: Some(palette.background.weak.color.into()), -            border_width: 2.0, -            border_color: palette.background.strong.color, -            ..Default::default() -        } -    } - -    pub fn pane_focused(theme: &Theme) -> container::Appearance { -        let palette = theme.extended_palette(); +fn window_view<'a>( +    id: window::Id, +    scale_input: &'a str, +    title: &'a str, +) -> Element<'a, Message> { +    let scale_input = column![ +        text("Window scale factor:"), +        text_input("Window Scale", scale_input, move |msg| { +            Message::ScaleInputChanged(id, msg) +        }) +        .on_submit(Message::ScaleChanged(id, scale_input.to_string())) +    ]; + +    let title_input = column![ +        text("Window title:"), +        text_input("Window Title", title, move |msg| { +            Message::TitleChanged(id, msg) +        }) +    ]; + +    let new_window_button = +        button(text("New Window")).on_press(Message::NewWindow); + +    let content = scrollable( +        column![scale_input, title_input, new_window_button] +            .spacing(50) +            .width(Length::Fill) +            .align_items(Alignment::Center), +    ); -        container::Appearance { -            background: Some(palette.background.weak.color.into()), -            border_width: 2.0, -            border_color: palette.primary.strong.color, -            ..Default::default() -        } -    } +    container(content).width(200).center_x().into()  } diff --git a/examples/multi_window_panes/Cargo.toml b/examples/multi_window_panes/Cargo.toml new file mode 100644 index 00000000..1b3f3ec6 --- /dev/null +++ b/examples/multi_window_panes/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "multi_window_panes" +version = "0.1.0" +authors = ["Richard Custodio <richardsoncusto@gmail.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug", "multi-window", "tokio"] } +env_logger = "0.10.0" +iced_native = { path = "../../native" } +iced_lazy = { path = "../../lazy" } diff --git a/examples/multi_window_panes/src/main.rs b/examples/multi_window_panes/src/main.rs new file mode 100644 index 00000000..b8b63769 --- /dev/null +++ b/examples/multi_window_panes/src/main.rs @@ -0,0 +1,624 @@ +use iced::alignment::{self, Alignment}; +use iced::{executor, time}; +use iced::keyboard; +use iced::multi_window::Application; +use iced::theme::{self, Theme}; +use iced::widget::pane_grid::{self, PaneGrid}; +use iced::widget::{ +    button, column, container, pick_list, row, scrollable, text, text_input, +}; +use iced::window; +use iced::{Color, Command, Element, Length, Settings, Size, Subscription}; +use iced_lazy::responsive; +use iced_native::{event, subscription, Event}; + +use iced_native::widget::scrollable::{Properties, RelativeOffset}; +use iced_native::window::Id; +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +pub fn main() -> iced::Result { +    env_logger::init(); + +    Example::run(Settings::default()) +} + +struct Example { +    windows: HashMap<window::Id, Window>, +    panes_created: usize, +    count: usize, +    _focused: window::Id, +} + +#[derive(Debug)] +struct Window { +    title: String, +    scale: f64, +    panes: pane_grid::State<Pane>, +    focus: Option<pane_grid::Pane>, +} + +#[derive(Debug, Clone)] +enum Message { +    Window(window::Id, WindowMessage), +    CountIncremented(Instant), +} + +#[derive(Debug, Clone)] +enum WindowMessage { +    Split(pane_grid::Axis, pane_grid::Pane), +    SplitFocused(pane_grid::Axis), +    FocusAdjacent(pane_grid::Direction), +    Clicked(pane_grid::Pane), +    Dragged(pane_grid::DragEvent), +    PopOut(pane_grid::Pane), +    Resized(pane_grid::ResizeEvent), +    TitleChanged(String), +    ToggleMoving(pane_grid::Pane), +    TogglePin(pane_grid::Pane), +    Close(pane_grid::Pane), +    CloseFocused, +    SelectedWindow(pane_grid::Pane, SelectableWindow), +    CloseWindow, +    SnapToggle, +} + +impl Application for Example { +    type Executor = executor::Default; +    type Message = Message; +    type Theme = Theme; +    type Flags = (); + +    fn new(_flags: ()) -> (Self, Command<Message>) { +        let (panes, _) = +            pane_grid::State::new(Pane::new(0, pane_grid::Axis::Horizontal)); +        let window = Window { +            panes, +            focus: None, +            title: String::from("Default window"), +            scale: 1.0, +        }; + +        ( +            Example { +                windows: HashMap::from([(window::Id::MAIN, window)]), +                panes_created: 1, +                count: 0, +                _focused: window::Id::MAIN, +            }, +            Command::none(), +        ) +    } + +    fn title(&self, window: window::Id) -> String { +        self.windows +            .get(&window) +            .map(|w| w.title.clone()) +            .unwrap_or(String::from("New Window")) +    } + +    fn update(&mut self, message: Message) -> Command<Message> { +        match message { +            Message::Window(id, message) => match message { +                WindowMessage::SnapToggle => { +                    let window = self.windows.get_mut(&id).unwrap(); + +                    if let Some(focused) = &window.focus { +                        let pane = window.panes.get_mut(focused).unwrap(); + +                        let cmd = scrollable::snap_to( +                            pane.scrollable_id.clone(), +                            if pane.snapped { +                                RelativeOffset::START +                            } else { +                                RelativeOffset::END +                            }, +                        ); + +                        pane.snapped = !pane.snapped; +                        return cmd; +                    } +                } +                WindowMessage::Split(axis, pane) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    let result = window.panes.split( +                        axis, +                        &pane, +                        Pane::new(self.panes_created, axis), +                    ); + +                    if let Some((pane, _)) = result { +                        window.focus = Some(pane); +                    } + +                    self.panes_created += 1; +                } +                WindowMessage::SplitFocused(axis) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    if let Some(pane) = window.focus { +                        let result = window.panes.split( +                            axis, +                            &pane, +                            Pane::new(self.panes_created, axis), +                        ); + +                        if let Some((pane, _)) = result { +                            window.focus = Some(pane); +                        } + +                        self.panes_created += 1; +                    } +                } +                WindowMessage::FocusAdjacent(direction) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    if let Some(pane) = window.focus { +                        if let Some(adjacent) = +                            window.panes.adjacent(&pane, direction) +                        { +                            window.focus = Some(adjacent); +                        } +                    } +                } +                WindowMessage::Clicked(pane) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    window.focus = Some(pane); +                } +                WindowMessage::CloseWindow => { +                    let _ = self.windows.remove(&id); +                    return window::close(id); +                } +                WindowMessage::Resized(pane_grid::ResizeEvent { split, ratio }) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    window.panes.resize(&split, ratio); +                } +                WindowMessage::SelectedWindow(pane, selected) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    let (mut pane, _) = window.panes.close(&pane).unwrap(); +                    pane.is_moving = false; + +                    if let Some(window) = self.windows.get_mut(&selected.0) { +                        let (&first_pane, _) = window.panes.iter().next().unwrap(); +                        let result = +                            window.panes.split(pane.axis, &first_pane, pane); + +                        if let Some((pane, _)) = result { +                            window.focus = Some(pane); +                        } +                    } +                } +                WindowMessage::ToggleMoving(pane) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    if let Some(pane) = window.panes.get_mut(&pane) { +                        pane.is_moving = !pane.is_moving; +                    } +                } +                WindowMessage::TitleChanged(title) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    window.title = title; +                } +                WindowMessage::PopOut(pane) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    if let Some((popped, sibling)) = window.panes.close(&pane) { +                        window.focus = Some(sibling); + +                        let (panes, _) = pane_grid::State::new(popped); +                        let window = Window { +                            panes, +                            focus: None, +                            title: format!("New window ({})", self.windows.len()), +                            scale: 1.0 + (self.windows.len() as f64 / 10.0), +                        }; + +                        let window_id = window::Id::new(self.windows.len()); +                        self.windows.insert(window_id, window); +                        return window::spawn(window_id, Default::default()); +                    } +                } +                WindowMessage::Dragged(pane_grid::DragEvent::Dropped { +                                           pane, +                                           target, +                                       }) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    window.panes.swap(&pane, &target); +                } +                // WindowMessage::Dragged(pane_grid::DragEvent::Picked { pane }) => { +                //     println!("Picked {pane:?}"); +                // } +                WindowMessage::Dragged(_) => {} +                WindowMessage::TogglePin(pane) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    if let Some(Pane { is_pinned, .. }) = +                        window.panes.get_mut(&pane) +                    { +                        *is_pinned = !*is_pinned; +                    } +                } +                WindowMessage::Close(pane) => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    if let Some((_, sibling)) = window.panes.close(&pane) { +                        window.focus = Some(sibling); +                    } +                } +                WindowMessage::CloseFocused => { +                    let window = self.windows.get_mut(&id).unwrap(); +                    if let Some(pane) = window.focus { +                        if let Some(Pane { is_pinned, .. }) = +                            window.panes.get(&pane) +                        { +                            if !is_pinned { +                                if let Some((_, sibling)) = +                                    window.panes.close(&pane) +                                { +                                    window.focus = Some(sibling); +                                } +                            } +                        } +                    } +                } +            }, +            Message::CountIncremented(_) => { +                self.count += 1; +            } +        } + +        Command::none() +    } + +    fn subscription(&self) -> Subscription<Message> { +        Subscription::batch(vec![ +            subscription::events_with(|event, status| { +                if let event::Status::Captured = status { +                    return None; +                } + +                match event { +                    Event::Keyboard(keyboard::Event::KeyPressed { +                                        modifiers, +                                        key_code, +                                    }) if modifiers.command() => { +                        handle_hotkey(key_code).map(|message| { +                            Message::Window(window::Id::new(0usize), message) +                        }) +                    } // TODO(derezzedex) +                    _ => None, +                } +            }), +            time::every(Duration::from_secs(1)).map(Message::CountIncremented), +        ]) +    } + +    fn view(&self, window: window::Id) -> Element<Message> { +        let window_id = window; + +        if let Some(window) = self.windows.get(&window) { +            let focus = window.focus; +            let total_panes = window.panes.len(); + +            let window_controls = row![ +                text_input( +                    "Window title", +                    &window.title, +                    WindowMessage::TitleChanged, +                ), +                button(text("Close")) +                    .on_press(WindowMessage::CloseWindow) +                    .style(theme::Button::Destructive), +            ] +            .spacing(5) +            .align_items(Alignment::Center); + +            let pane_grid = PaneGrid::new(&window.panes, |id, pane, _| { +                let is_focused = focus == Some(id); + +                let pin_button = button( +                    text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), +                ) +                .on_press(WindowMessage::TogglePin(id)) +                .padding(3); + +                let title = row![ +                    pin_button, +                    "Pane", +                    text(pane.id.to_string()).style(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, +                        pane.is_moving, +                        &window.title, +                        window_id, +                        &self.windows, +                    )) +                    .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, +                        pane.scrollable_id.clone(), +                        self.count, +                        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(WindowMessage::Clicked) +            .on_drag(WindowMessage::Dragged) +            .on_resize(10, WindowMessage::Resized); + +            let content: Element<_> = column![window_controls, pane_grid] +                .width(Length::Fill) +                .height(Length::Fill) +                .padding(10) +                .into(); + +            return content +                .map(move |message| Message::Window(window_id, message)); +        } + +        container(text("This shouldn't be possible!").size(20)) +            .center_x() +            .center_y() +            .into() +    } + +    fn close_requested(&self, window: window::Id) -> Self::Message { +        Message::Window(window, WindowMessage::CloseWindow) +    } + +    fn scale_factor(&self, window: Id) -> f64 { +        self.windows.get(&window).map(|w| w.scale).unwrap_or(1.0) +    } +} + +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_code: keyboard::KeyCode) -> Option<WindowMessage> { +    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(WindowMessage::SplitFocused(Axis::Vertical)), +        KeyCode::H => Some(WindowMessage::SplitFocused(Axis::Horizontal)), +        KeyCode::W => Some(WindowMessage::CloseFocused), +        _ => direction.map(WindowMessage::FocusAdjacent), +    } +} + +#[derive(Debug, Clone)] +struct SelectableWindow(window::Id, String); + +impl PartialEq for SelectableWindow { +    fn eq(&self, other: &Self) -> bool { +        self.0 == other.0 +    } +} + +impl Eq for SelectableWindow {} + +impl std::fmt::Display for SelectableWindow { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        self.1.fmt(f) +    } +} + +#[derive(Debug)] +struct Pane { +    id: usize, +    pub scrollable_id: scrollable::Id, +    pub axis: pane_grid::Axis, +    pub is_pinned: bool, +    pub is_moving: bool, +    pub snapped: bool, +} + +impl Pane { +    fn new(id: usize, axis: pane_grid::Axis) -> Self { +        Self { +            id, +            scrollable_id: scrollable::Id::unique(), +            axis, +            is_pinned: false, +            is_moving: false, +            snapped: false, +        } +    } +} + +fn view_content<'a>( +    pane: pane_grid::Pane, +    scrollable_id: scrollable::Id, +    count: usize, +    total_panes: usize, +    is_pinned: bool, +    size: Size, +) -> Element<'a, WindowMessage> { +    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 mut controls = column![ +        button( +            "Split horizontally", +            WindowMessage::Split(pane_grid::Axis::Horizontal, pane), +        ), +        button( +            "Split vertically", +            WindowMessage::Split(pane_grid::Axis::Vertical, pane), +        ), +        button("Snap", WindowMessage::SnapToggle,) +    ] +    .spacing(5) +    .max_width(150); + +    if total_panes > 1 && !is_pinned { +        controls = controls.push( +            button("Close", WindowMessage::Close(pane)) +                .style(theme::Button::Destructive), +        ); +    } + +    let content = column![ +        text(format!("{}x{}", size.width, size.height)).size(24), +        controls, +        text(format!("{count}")).size(48), +    ] +    .width(Length::Fill) +    .height(800) +    .spacing(10) +    .align_items(Alignment::Center); + +    container( +        scrollable(content) +            .height(Length::Fill) +            .vertical_scroll(Properties::new()) +            .id(scrollable_id), +    ) +    .width(Length::Fill) +    .height(Length::Fill) +    .padding(5) +    .center_y() +    .into() +} + +fn view_controls<'a>( +    pane: pane_grid::Pane, +    total_panes: usize, +    is_pinned: bool, +    is_moving: bool, +    window_title: &'a str, +    window_id: window::Id, +    windows: &HashMap<window::Id, Window>, +) -> Element<'a, WindowMessage> { +    let window_selector = { +        let options: Vec<_> = windows +            .iter() +            .map(|(id, window)| SelectableWindow(*id, window.title.clone())) +            .collect(); +        pick_list( +            options, +            Some(SelectableWindow(window_id, window_title.to_string())), +            move |window| WindowMessage::SelectedWindow(pane, window), +        ) +    }; + +    let mut move_to = button(text("Move to").size(14)).padding(3); + +    let mut pop_out = button(text("Pop Out").size(14)).padding(3); + +    let mut close = button(text("Close").size(14)) +        .style(theme::Button::Destructive) +        .padding(3); + +    if total_panes > 1 && !is_pinned { +        close = close.on_press(WindowMessage::Close(pane)); +        pop_out = pop_out.on_press(WindowMessage::PopOut(pane)); +    } + +    if windows.len() > 1 && total_panes > 1 && !is_pinned { +        move_to = move_to.on_press(WindowMessage::ToggleMoving(pane)); +    } + +    let mut content = row![].spacing(10); +    if is_moving { +        content = content.push(pop_out).push(window_selector).push(close); +    } else { +        content = content.push(pop_out).push(move_to).push(close); +    } + +    content.into() +} + +mod style { +    use iced::widget::container; +    use iced::Theme; + +    pub fn title_bar_active(theme: &Theme) -> container::Appearance { +        let palette = theme.extended_palette(); + +        container::Appearance { +            text_color: Some(palette.background.strong.text), +            background: Some(palette.background.strong.color.into()), +            ..Default::default() +        } +    } + +    pub fn title_bar_focused(theme: &Theme) -> container::Appearance { +        let palette = theme.extended_palette(); + +        container::Appearance { +            text_color: Some(palette.primary.strong.text), +            background: Some(palette.primary.strong.color.into()), +            ..Default::default() +        } +    } + +    pub fn pane_active(theme: &Theme) -> container::Appearance { +        let palette = theme.extended_palette(); + +        container::Appearance { +            background: Some(palette.background.weak.color.into()), +            border_width: 2.0, +            border_color: palette.background.strong.color, +            ..Default::default() +        } +    } + +    pub fn pane_focused(theme: &Theme) -> container::Appearance { +        let palette = theme.extended_palette(); + +        container::Appearance { +            background: Some(palette.background.weak.color.into()), +            border_width: 2.0, +            border_color: palette.primary.strong.color, +            ..Default::default() +        } +    } +} | 
