use iced::keyboard;
use iced::widget::pane_grid::{self, PaneGrid};
use iced::widget::{button, column, container, responsive, row, scrollable, text};
use iced::{Border, Center, Element, Fill, Length, Size, Subscription, Task, Theme};
pub fn main() -> iced::Result {
iced::application("rae pane grid", Example::update, Example::view)
.subscription(Example::subscription)
.window_size(Size::new(960.0, 640.0))
.run_with(|| (Example::new(), Task::none()))
}
struct Example {
panes: pane_grid::State<Pane>,
panes_created: usize,
focus: Option<pane_grid::Pane>,
}
#[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));
Self {
panes,
panes_created: 1,
focus: None,
}
}
fn update(&mut self, message: Message) {
match message {
Message::Split(axis, pane) => {
if let Some((pane, _)) = self.panes.split(axis, pane, Pane::new(self.panes_created))
{
self.focus = Some(pane);
}
self.panes_created += 1;
}
Message::SplitFocused(axis) => {
if let Some(pane) = self.focus {
if let Some((pane, _)) =
self.panes.split(axis, pane, Pane::new(self.panes_created))
{
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<Message> {
keyboard::on_key_press(|key, modifiers| {
if modifiers.command() {
handle_hotkey(key)
} else {
None
}
})
}
fn view(&self) -> Element<'_, Message> {
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 title = row![
button(if pane.is_pinned { "Unpin" } else { "Pin" })
.on_press(Message::TogglePin(id))
.padding(3),
text(format!("Pane {}", pane.id)).size(16),
if is_focused {
text("focused").size(14)
} else {
text("").size(14)
},
]
.spacing(8)
.align_y(Center);
let title_bar = pane_grid::TitleBar::new(title)
.controls(pane_grid::Controls::dynamic(
view_controls(id, total_panes, pane.is_pinned, is_maximized),
button("X").padding(3).on_press_maybe(
(total_panes > 1 && !pane.is_pinned).then_some(Message::Close(id)),
),
))
.padding(8)
.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(Fill)
.height(Fill)
.spacing(10)
.on_click(Message::Clicked)
.on_drag(Message::Dragged)
.on_resize(10, Message::Resized);
container(pane_grid).padding(10).into()
}
}
#[derive(Clone, Copy)]
struct Pane {
id: usize,
is_pinned: bool,
}
impl Pane {
fn new(id: usize) -> Self {
Self {
id,
is_pinned: false,
}
}
}
fn handle_hotkey(key: keyboard::Key) -> Option<Message> {
use iced::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) => match key {
key::Named::ArrowUp => Some(Message::FocusAdjacent(Direction::Up)),
key::Named::ArrowDown => Some(Message::FocusAdjacent(Direction::Down)),
key::Named::ArrowLeft => Some(Message::FocusAdjacent(Direction::Left)),
key::Named::ArrowRight => Some(Message::FocusAdjacent(Direction::Right)),
_ => None,
},
_ => None,
}
}
fn view_content<'a>(
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
size: Size,
) -> Element<'a, Message> {
let action = |label, message| {
button(text(label).width(Fill).align_x(Center).size(16))
.padding(8)
.on_press(message)
};
let mut controls = column![
action(
"Split horizontally",
Message::Split(pane_grid::Axis::Horizontal, pane),
),
action(
"Split vertically",
Message::Split(pane_grid::Axis::Vertical, pane),
),
];
if total_panes > 1 && !is_pinned {
controls = controls.push(action("Close", Message::Close(pane)));
}
let controls = controls.spacing(6).width(Length::Fixed(190.0));
let content = column![
text(format!("{:.0} x {:.0}", size.width, size.height)).size(24),
controls,
]
.spacing(12)
.width(Fill)
.align_x(Center);
container(scrollable(content)).padding(8).into()
}
fn view_controls<'a>(
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
is_maximized: bool,
) -> Element<'a, Message> {
let mut controls = row![].spacing(6);
if total_panes > 1 {
let (label, message) = if is_maximized {
("Restore", Message::Restore)
} else {
("Maximize", Message::Maximize(pane))
};
controls = controls.push(
button(label)
.style(button::secondary)
.padding(3)
.on_press(message),
);
}
let close = button("Close")
.style(button::danger)
.padding(3)
.on_press_maybe((total_panes > 1 && !is_pinned).then_some(Message::Close(pane)));
controls.push(close).into()
}
mod style {
use super::*;
use iced::widget::container;
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()
}
}
}