diff options
Diffstat (limited to 'examples/multi_window_panes')
| -rw-r--r-- | examples/multi_window_panes/Cargo.toml | 12 | ||||
| -rw-r--r-- | examples/multi_window_panes/src/main.rs | 639 | 
2 files changed, 651 insertions, 0 deletions
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..b1d0a3bc --- /dev/null +++ b/examples/multi_window_panes/src/main.rs @@ -0,0 +1,639 @@ +use iced::alignment::{self, Alignment}; +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::{executor, time}; +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, +    theme: Theme, +    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, +            theme: Theme::default(), +        }; + +        ( +            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), +                            theme: if self.windows.len() % 2 == 0 { +                                Theme::Light +                            } else { +                                Theme::Dark +                            }, +                        }; + +                        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(_) => {} +                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) +    } + +    fn theme(&self, window: Id) -> Theme { +        self.windows.get(&window).expect("Window not found!").theme.clone() +    } +} + +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() +        } +    } +}  | 
