Added simpler MW example
This commit is contained in:
parent
8ba1843080
commit
ce4b2c93d9
6 changed files with 741 additions and 559 deletions
|
|
@ -1,13 +1,9 @@
|
||||||
[package]
|
[package]
|
||||||
name = "multi_window"
|
name = "multi_window"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Richard Custodio <richardsoncusto@gmail.com>"]
|
authors = ["Bingus <shankern@protonmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["debug", "multi-window", "tokio"] }
|
iced = { path = "../..", features = ["debug", "multi-window"] }
|
||||||
env_logger = "0.10.0"
|
|
||||||
iced_native = { path = "../../native" }
|
|
||||||
iced_lazy = { path = "../../lazy" }
|
|
||||||
|
|
@ -1,90 +1,50 @@
|
||||||
use iced::alignment::{self, Alignment};
|
use iced::multi_window::{self, Application};
|
||||||
use iced::{executor, time};
|
use iced::widget::{button, column, container, scrollable, text, text_input};
|
||||||
use iced::keyboard;
|
use iced::{
|
||||||
use iced::multi_window::Application;
|
executor, window, Alignment, Command, Element, Length, Settings, Theme,
|
||||||
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::collections::HashMap;
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
|
fn main() -> iced::Result {
|
||||||
Example::run(Settings::default())
|
Example::run(Settings::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct Example {
|
struct Example {
|
||||||
|
windows_count: usize,
|
||||||
windows: HashMap<window::Id, Window>,
|
windows: HashMap<window::Id, Window>,
|
||||||
panes_created: usize,
|
|
||||||
count: usize,
|
|
||||||
_focused: window::Id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Window {
|
struct Window {
|
||||||
|
id: window::Id,
|
||||||
title: String,
|
title: String,
|
||||||
scale: f64,
|
scale_input: String,
|
||||||
panes: pane_grid::State<Pane>,
|
current_scale: f64,
|
||||||
focus: Option<pane_grid::Pane>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Window(window::Id, WindowMessage),
|
ScaleInputChanged(window::Id, String),
|
||||||
CountIncremented(Instant),
|
ScaleChanged(window::Id, String),
|
||||||
|
TitleChanged(window::Id, String),
|
||||||
|
CloseWindow(window::Id),
|
||||||
|
NewWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl multi_window::Application for Example {
|
||||||
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 Executor = executor::Default;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
type Theme = Theme;
|
type Theme = Theme;
|
||||||
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, pane_grid::Axis::Horizontal));
|
|
||||||
let window = Window {
|
|
||||||
panes,
|
|
||||||
focus: None,
|
|
||||||
title: String::from("Default window"),
|
|
||||||
scale: 1.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Example {
|
Example {
|
||||||
windows: HashMap::from([(window::Id::MAIN, window)]),
|
windows_count: 0,
|
||||||
panes_created: 1,
|
windows: HashMap::from([(
|
||||||
count: 0,
|
window::Id::MAIN,
|
||||||
_focused: window::Id::MAIN,
|
Window::new(window::Id::MAIN),
|
||||||
|
)]),
|
||||||
},
|
},
|
||||||
Command::none(),
|
Command::none(),
|
||||||
)
|
)
|
||||||
|
|
@ -93,530 +53,118 @@ impl Application for Example {
|
||||||
fn title(&self, window: window::Id) -> String {
|
fn title(&self, window: window::Id) -> String {
|
||||||
self.windows
|
self.windows
|
||||||
.get(&window)
|
.get(&window)
|
||||||
.map(|w| w.title.clone())
|
.map(|window| window.title.clone())
|
||||||
.unwrap_or(String::from("New Window"))
|
.unwrap_or("Example".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Window(id, message) => match message {
|
Message::ScaleInputChanged(id, scale) => {
|
||||||
WindowMessage::SnapToggle => {
|
let window =
|
||||||
let window = self.windows.get_mut(&id).unwrap();
|
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!");
|
||||||
|
|
||||||
if let Some(focused) = &window.focus {
|
window.current_scale = scale
|
||||||
let pane = window.panes.get_mut(focused).unwrap();
|
.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 cmd = scrollable::snap_to(
|
window.title = title;
|
||||||
pane.scrollable_id.clone(),
|
}
|
||||||
if pane.snapped {
|
Message::CloseWindow(id) => {
|
||||||
RelativeOffset::START
|
return window::close(id);
|
||||||
} else {
|
}
|
||||||
RelativeOffset::END
|
Message::NewWindow => {
|
||||||
},
|
self.windows_count += 1;
|
||||||
);
|
let id = window::Id::new(self.windows_count);
|
||||||
|
self.windows.insert(id, Window::new(id));
|
||||||
|
|
||||||
pane.snapped = !pane.snapped;
|
return window::spawn(id, window::Settings::default());
|
||||||
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()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn view(&self, window: window::Id) -> Element<Message> {
|
||||||
Subscription::batch(vec![
|
let window = self
|
||||||
subscription::events_with(|event, status| {
|
.windows
|
||||||
if let event::Status::Captured = status {
|
.get(&window)
|
||||||
return None;
|
.map(|window| window.view())
|
||||||
}
|
.unwrap();
|
||||||
|
|
||||||
match event {
|
container(window)
|
||||||
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
|
|
||||||
});
|
|
||||||
|
|
||||||
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)
|
.width(Length::Fill)
|
||||||
.height(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_x()
|
||||||
.center_y()
|
.center_y()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scale_factor(&self, window: window::Id) -> f64 {
|
||||||
|
self.windows
|
||||||
|
.get(&window)
|
||||||
|
.map(|window| window.current_scale)
|
||||||
|
.unwrap_or(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
fn close_requested(&self, window: window::Id) -> Self::Message {
|
fn close_requested(&self, window: window::Id) -> Self::Message {
|
||||||
Message::Window(window, WindowMessage::CloseWindow)
|
Message::CloseWindow(window)
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
impl Window {
|
||||||
0xFF as f32 / 255.0,
|
fn new(id: window::Id) -> Self {
|
||||||
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 {
|
Self {
|
||||||
id,
|
id,
|
||||||
scrollable_id: scrollable::Id::unique(),
|
title: "Window".to_string(),
|
||||||
axis,
|
scale_input: "1.0".to_string(),
|
||||||
is_pinned: false,
|
current_scale: 1.0,
|
||||||
is_moving: false,
|
|
||||||
snapped: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Element<Message> {
|
||||||
|
window_view(self.id, &self.scale_input, &self.title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_content<'a>(
|
fn window_view<'a>(
|
||||||
pane: pane_grid::Pane,
|
id: window::Id,
|
||||||
scrollable_id: scrollable::Id,
|
scale_input: &'a str,
|
||||||
count: usize,
|
title: &'a str,
|
||||||
total_panes: usize,
|
) -> Element<'a, Message> {
|
||||||
is_pinned: bool,
|
let scale_input = column![
|
||||||
size: Size,
|
text("Window scale factor:"),
|
||||||
) -> Element<'a, WindowMessage> {
|
text_input("Window Scale", scale_input, move |msg| {
|
||||||
let button = |label, message| {
|
Message::ScaleInputChanged(id, msg)
|
||||||
button(
|
})
|
||||||
text(label)
|
.on_submit(Message::ScaleChanged(id, scale_input.to_string()))
|
||||||
.width(Length::Fill)
|
];
|
||||||
.horizontal_alignment(alignment::Horizontal::Center)
|
|
||||||
.size(16),
|
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.padding(8)
|
|
||||||
.on_press(message)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut controls = column![
|
let title_input = column![
|
||||||
button(
|
text("Window title:"),
|
||||||
"Split horizontally",
|
text_input("Window Title", title, move |msg| {
|
||||||
WindowMessage::Split(pane_grid::Axis::Horizontal, pane),
|
Message::TitleChanged(id, msg)
|
||||||
),
|
})
|
||||||
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 {
|
let new_window_button =
|
||||||
controls = controls.push(
|
button(text("New Window")).on_press(Message::NewWindow);
|
||||||
button("Close", WindowMessage::Close(pane))
|
|
||||||
.style(theme::Button::Destructive),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = column![
|
let content = scrollable(
|
||||||
text(format!("{}x{}", size.width, size.height)).size(24),
|
column![scale_input, title_input, new_window_button]
|
||||||
controls,
|
.spacing(50)
|
||||||
text(format!("{count}")).size(48),
|
.width(Length::Fill)
|
||||||
]
|
.align_items(Alignment::Center),
|
||||||
.width(Length::Fill)
|
);
|
||||||
.height(800)
|
|
||||||
.spacing(10)
|
|
||||||
.align_items(Alignment::Center);
|
|
||||||
|
|
||||||
container(
|
container(content).width(200).center_x().into()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
examples/multi_window_panes/Cargo.toml
Normal file
12
examples/multi_window_panes/Cargo.toml
Normal file
|
|
@ -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" }
|
||||||
624
examples/multi_window_panes/src/main.rs
Normal file
624
examples/multi_window_panes/src/main.rs
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,8 @@ use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
/// The ID of the window.
|
/// The ID of the window.
|
||||||
|
///
|
||||||
|
/// Internally Iced uses `window::Id::MAIN` as the first window spawned.
|
||||||
pub struct Id(u64);
|
pub struct Id(u64);
|
||||||
|
|
||||||
impl Id {
|
impl Id {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ where
|
||||||
///
|
///
|
||||||
/// This title can be dynamic! The runtime will automatically update the
|
/// This title can be dynamic! The runtime will automatically update the
|
||||||
/// title of your application when necessary.
|
/// title of your application when necessary.
|
||||||
fn title(&self, window_id: window::Id) -> String;
|
fn title(&self, window: window::Id) -> String;
|
||||||
|
|
||||||
/// Returns the current [`Theme`] of the [`Application`].
|
/// Returns the current [`Theme`] of the [`Application`].
|
||||||
fn theme(&self) -> <Self::Renderer as crate::Renderer>::Theme;
|
fn theme(&self) -> <Self::Renderer as crate::Renderer>::Theme;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue