create multi-windowed pane_grid example

This commit is contained in:
Richard 2022-07-21 09:52:55 -03:00 committed by bungoboingo
parent 2fe58e1261
commit 3d901d5f1f

View file

@ -1,36 +1,55 @@
use iced::alignment::{self, Alignment}; use iced::alignment::{self, Alignment};
use iced::executor; use iced::executor;
use iced::keyboard; use iced::keyboard;
use iced::multi_window::Application;
use iced::theme::{self, Theme}; use iced::theme::{self, Theme};
use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::pane_grid::{self, PaneGrid};
use iced::widget::{button, column, container, row, scrollable, text}; use iced::widget::{
use iced::{ button, column, container, pick_list, row, scrollable, text, text_input,
Application, Color, Command, Element, Length, Settings, Size, Subscription,
}; };
use iced::window;
use iced::{Color, Command, Element, Length, Settings, Size, Subscription};
use iced_lazy::responsive; use iced_lazy::responsive;
use iced_native::{event, subscription, Event}; use iced_native::{event, subscription, Event};
use std::collections::HashMap;
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
Example::run(Settings::default()) Example::run(Settings::default())
} }
struct Example { struct Example {
panes: pane_grid::State<Pane>, windows: HashMap<window::Id, Window>,
panes_created: usize, panes_created: usize,
_focused: window::Id,
}
struct Window {
title: String,
panes: pane_grid::State<Pane>,
focus: Option<pane_grid::Pane>, focus: Option<pane_grid::Pane>,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
enum Message { enum Message {
Window(window::Id, WindowMessage),
}
#[derive(Debug, Clone)]
enum WindowMessage {
Split(pane_grid::Axis, pane_grid::Pane), Split(pane_grid::Axis, pane_grid::Pane),
SplitFocused(pane_grid::Axis), SplitFocused(pane_grid::Axis),
FocusAdjacent(pane_grid::Direction), FocusAdjacent(pane_grid::Direction),
Clicked(pane_grid::Pane), Clicked(pane_grid::Pane),
Dragged(pane_grid::DragEvent), Dragged(pane_grid::DragEvent),
PopOut(pane_grid::Pane),
Resized(pane_grid::ResizeEvent), Resized(pane_grid::ResizeEvent),
TitleChanged(String),
ToggleMoving(pane_grid::Pane),
TogglePin(pane_grid::Pane), TogglePin(pane_grid::Pane),
Close(pane_grid::Pane), Close(pane_grid::Pane),
CloseFocused, CloseFocused,
SelectedWindow(pane_grid::Pane, SelectableWindow),
} }
impl Application for Example { impl Application for Example {
@ -40,93 +59,158 @@ impl Application for Example {
type Flags = (); type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) { fn new(_flags: ()) -> (Self, Command<Message>) {
let (panes, _) = pane_grid::State::new(Pane::new(0)); let (panes, _) =
pane_grid::State::new(Pane::new(0, pane_grid::Axis::Horizontal));
let window = Window {
panes,
focus: None,
title: String::from("Default window"),
};
( (
Example { Example {
panes, windows: HashMap::from([(window::Id::new(0usize), window)]),
panes_created: 1, panes_created: 1,
focus: None, _focused: window::Id::new(0usize),
}, },
Command::none(), Command::none(),
) )
} }
fn title(&self) -> String { fn title(&self) -> String {
String::from("Pane grid - Iced") String::from("Multi windowed pane grid - Iced")
} }
fn update(&mut self, message: Message) -> Command<Message> { fn update(&mut self, message: Message) -> Command<Message> {
let Message::Window(id, message) = message;
match message { match message {
Message::Split(axis, pane) => { WindowMessage::Split(axis, pane) => {
let result = self.panes.split( let window = self.windows.get_mut(&id).unwrap();
let result = window.panes.split(
axis, axis,
&pane, &pane,
Pane::new(self.panes_created), Pane::new(self.panes_created, axis),
); );
if let Some((pane, _)) = result { if let Some((pane, _)) = result {
self.focus = Some(pane); window.focus = Some(pane);
} }
self.panes_created += 1; self.panes_created += 1;
} }
Message::SplitFocused(axis) => { WindowMessage::SplitFocused(axis) => {
if let Some(pane) = self.focus { let window = self.windows.get_mut(&id).unwrap();
let result = self.panes.split( if let Some(pane) = window.focus {
let result = window.panes.split(
axis, axis,
&pane, &pane,
Pane::new(self.panes_created), Pane::new(self.panes_created, axis),
); );
if let Some((pane, _)) = result { if let Some((pane, _)) = result {
self.focus = Some(pane); window.focus = Some(pane);
} }
self.panes_created += 1; self.panes_created += 1;
} }
} }
Message::FocusAdjacent(direction) => { WindowMessage::FocusAdjacent(direction) => {
if let Some(pane) = self.focus { let window = self.windows.get_mut(&id).unwrap();
if let Some(pane) = window.focus {
if let Some(adjacent) = if let Some(adjacent) =
self.panes.adjacent(&pane, direction) window.panes.adjacent(&pane, direction)
{ {
self.focus = Some(adjacent); window.focus = Some(adjacent);
} }
} }
} }
Message::Clicked(pane) => { WindowMessage::Clicked(pane) => {
self.focus = Some(pane); let window = self.windows.get_mut(&id).unwrap();
window.focus = Some(pane);
} }
Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { WindowMessage::Resized(pane_grid::ResizeEvent { split, ratio }) => {
self.panes.resize(&split, ratio); let window = self.windows.get_mut(&id).unwrap();
window.panes.resize(&split, ratio);
} }
Message::Dragged(pane_grid::DragEvent::Dropped { 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);
}
self.panes_created += 1;
}
}
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()),
};
self.windows
.insert(window::Id::new(self.windows.len()), window);
}
}
WindowMessage::Dragged(pane_grid::DragEvent::Dropped {
pane, pane,
target, target,
}) => { }) => {
self.panes.swap(&pane, &target); let window = self.windows.get_mut(&id).unwrap();
window.panes.swap(&pane, &target);
} }
Message::Dragged(_) => {} // WindowMessage::Dragged(pane_grid::DragEvent::Picked { pane }) => {
Message::TogglePin(pane) => { // println!("Picked {pane:?}");
if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&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; *is_pinned = !*is_pinned;
} }
} }
Message::Close(pane) => { WindowMessage::Close(pane) => {
if let Some((_, sibling)) = self.panes.close(&pane) { let window = self.windows.get_mut(&id).unwrap();
self.focus = Some(sibling); if let Some((_, sibling)) = window.panes.close(&pane) {
window.focus = Some(sibling);
} }
} }
Message::CloseFocused => { WindowMessage::CloseFocused => {
if let Some(pane) = self.focus { let window = self.windows.get_mut(&id).unwrap();
if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane) if let Some(pane) = window.focus {
if let Some(Pane { is_pinned, .. }) =
window.panes.get(&pane)
{ {
if !is_pinned { if !is_pinned {
if let Some((_, sibling)) = self.panes.close(&pane) if let Some((_, sibling)) =
window.panes.close(&pane)
{ {
self.focus = Some(sibling); window.focus = Some(sibling);
} }
} }
} }
@ -147,66 +231,106 @@ impl Application for Example {
Event::Keyboard(keyboard::Event::KeyPressed { Event::Keyboard(keyboard::Event::KeyPressed {
modifiers, modifiers,
key_code, key_code,
}) if modifiers.command() => handle_hotkey(key_code), }) if modifiers.command() => {
handle_hotkey(key_code).map(|message| {
Message::Window(window::Id::new(0usize), message)
})
} // TODO(derezzedex)
_ => None, _ => None,
} }
}) })
} }
fn view(&self) -> Element<Message> { fn windows(&self) -> Vec<(window::Id, iced::window::Settings)> {
let focus = self.focus; self.windows
let total_panes = self.panes.len(); .iter()
.map(|(&id, _window)| (id, iced::window::Settings::default()))
.collect()
}
let pane_grid = PaneGrid::new(&self.panes, |id, pane| { fn view(&self, window_id: window::Id) -> Element<Message> {
let is_focused = focus == Some(id); if let Some(window) = self.windows.get(&window_id) {
let focus = window.focus;
let total_panes = window.panes.len();
let pin_button = button( let window_controls = row![
text(if pane.is_pinned { "Unpin" } else { "Pin" }).size(14), text_input(
) "Window title",
.on_press(Message::TogglePin(id)) &window.title,
.padding(3); WindowMessage::TitleChanged,
),
let title = row![ button(text("Apply")).style(theme::Button::Primary),
pin_button,
"Pane",
text(pane.id.to_string()).style(if is_focused {
PANE_ID_COLOR_FOCUSED
} else {
PANE_ID_COLOR_UNFOCUSED
}),
] ]
.spacing(5); .spacing(5)
.align_items(Alignment::Center);
let title_bar = pane_grid::TitleBar::new(title) let pane_grid = PaneGrid::new(&window.panes, |id, pane| {
.controls(view_controls(id, total_panes, pane.is_pinned)) let is_focused = focus == Some(id);
.padding(10)
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, total_panes, pane.is_pinned, size)
}))
.title_bar(title_bar)
.style(if is_focused { .style(if is_focused {
style::title_bar_focused style::pane_focused
} else { } else {
style::title_bar_active style::pane_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) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.padding(10) .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() .into()
} }
} }
@ -222,7 +346,7 @@ const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb(
0x47 as f32 / 255.0, 0x47 as f32 / 255.0,
); );
fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> { fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<WindowMessage> {
use keyboard::KeyCode; use keyboard::KeyCode;
use pane_grid::{Axis, Direction}; use pane_grid::{Axis, Direction};
@ -235,23 +359,44 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}; };
match key_code { match key_code {
KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), KeyCode::V => Some(WindowMessage::SplitFocused(Axis::Vertical)),
KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), KeyCode::H => Some(WindowMessage::SplitFocused(Axis::Horizontal)),
KeyCode::W => Some(Message::CloseFocused), KeyCode::W => Some(WindowMessage::CloseFocused),
_ => direction.map(Message::FocusAdjacent), _ => 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)
} }
} }
struct Pane { struct Pane {
id: usize, id: usize,
pub axis: pane_grid::Axis,
pub is_pinned: bool, pub is_pinned: bool,
pub is_moving: bool,
} }
impl Pane { impl Pane {
fn new(id: usize) -> Self { fn new(id: usize, axis: pane_grid::Axis) -> Self {
Self { Self {
id, id,
axis,
is_pinned: false, is_pinned: false,
is_moving: false,
} }
} }
} }
@ -261,7 +406,7 @@ fn view_content<'a>(
total_panes: usize, total_panes: usize,
is_pinned: bool, is_pinned: bool,
size: Size, size: Size,
) -> Element<'a, Message> { ) -> Element<'a, WindowMessage> {
let button = |label, message| { let button = |label, message| {
button( button(
text(label) text(label)
@ -277,11 +422,11 @@ fn view_content<'a>(
let mut controls = column![ let mut controls = column![
button( button(
"Split horizontally", "Split horizontally",
Message::Split(pane_grid::Axis::Horizontal, pane), WindowMessage::Split(pane_grid::Axis::Horizontal, pane),
), ),
button( button(
"Split vertically", "Split vertically",
Message::Split(pane_grid::Axis::Vertical, pane), WindowMessage::Split(pane_grid::Axis::Vertical, pane),
) )
] ]
.spacing(5) .spacing(5)
@ -289,7 +434,7 @@ fn view_content<'a>(
if total_panes > 1 && !is_pinned { if total_panes > 1 && !is_pinned {
controls = controls.push( controls = controls.push(
button("Close", Message::Close(pane)) button("Close", WindowMessage::Close(pane))
.style(theme::Button::Destructive), .style(theme::Button::Destructive),
); );
} }
@ -314,16 +459,48 @@ fn view_controls<'a>(
pane: pane_grid::Pane, pane: pane_grid::Pane,
total_panes: usize, total_panes: usize,
is_pinned: bool, is_pinned: bool,
) -> Element<'a, Message> { is_moving: bool,
let mut button = button(text("Close").size(14)) 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) .style(theme::Button::Destructive)
.padding(3); .padding(3);
if total_panes > 1 && !is_pinned { if total_panes > 1 && !is_pinned {
button = button.on_press(Message::Close(pane)); close = close.on_press(WindowMessage::Close(pane));
pop_out = pop_out.on_press(WindowMessage::PopOut(pane));
} }
button.into() 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 { mod style {