Add grid with split button
This commit is contained in:
parent
9f1de060eb
commit
2866b8ea18
@ -1,8 +1,8 @@
|
||||
use iced::{
|
||||
event::{self, Event},
|
||||
keyboard,
|
||||
widget::{focus_next, focus_previous},
|
||||
Subscription, Task,
|
||||
widget::{focus_next, focus_previous, pane_grid},
|
||||
Length, Subscription, Task,
|
||||
};
|
||||
use tracing::{error, info};
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
@ -10,7 +10,7 @@ extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use liana::miniscript::bitcoin;
|
||||
use liana_ui::widget::{Column, Element};
|
||||
use liana_ui::widget::{Column, Container, Element};
|
||||
|
||||
pub mod pane;
|
||||
pub mod tab;
|
||||
@ -18,7 +18,8 @@ pub mod tab;
|
||||
use crate::{dir::LianaDirectory, logger::Logger, VERSION};
|
||||
|
||||
pub struct GUI {
|
||||
pane: pane::Pane,
|
||||
panes: pane_grid::State<pane::Pane>,
|
||||
focus: Option<pane_grid::Pane>,
|
||||
config: Config,
|
||||
// We may change the directory of log outputs later
|
||||
_logger: Logger,
|
||||
@ -33,9 +34,13 @@ pub enum Key {
|
||||
pub enum Message {
|
||||
CtrlC,
|
||||
FontLoaded(Result<(), iced::font::Error>),
|
||||
Pane(pane::Message),
|
||||
Pane(pane_grid::Pane, pane::Message),
|
||||
KeyPressed(Key),
|
||||
Event(iced::Event),
|
||||
|
||||
Clicked(pane_grid::Pane),
|
||||
Dragged(pane_grid::DragEvent),
|
||||
Resized(pane_grid::ResizeEvent),
|
||||
}
|
||||
|
||||
impl From<Result<(), iced::font::Error>> for Message {
|
||||
@ -65,10 +70,12 @@ impl GUI {
|
||||
);
|
||||
let mut cmds = vec![Task::perform(ctrl_c(), |_| Message::CtrlC)];
|
||||
let (pane, cmd) = pane::Pane::new(&config);
|
||||
cmds.push(cmd.map(Message::Pane));
|
||||
let (panes, focused_pane) = pane_grid::State::new(pane);
|
||||
cmds.push(cmd.map(move |msg| Message::Pane(focused_pane, msg)));
|
||||
(
|
||||
Self {
|
||||
pane,
|
||||
panes,
|
||||
focus: Some(focused_pane),
|
||||
config,
|
||||
_logger: logger,
|
||||
},
|
||||
@ -80,7 +87,9 @@ impl GUI {
|
||||
match message {
|
||||
Message::CtrlC
|
||||
| Message::Event(iced::Event::Window(iced::window::Event::CloseRequested)) => {
|
||||
self.pane.stop();
|
||||
for (_, pane) in self.panes.iter_mut() {
|
||||
pane.stop();
|
||||
}
|
||||
iced::window::get_latest().and_then(iced::window::close)
|
||||
}
|
||||
Message::KeyPressed(Key::Tab(shift)) => {
|
||||
@ -91,15 +100,84 @@ impl GUI {
|
||||
focus_next()
|
||||
}
|
||||
}
|
||||
Message::Pane(msg) => self.pane.update(msg, &self.config).map(Message::Pane),
|
||||
Message::Pane(pane_id, pane::Message::View(pane::ViewMessage::SplitTab(i))) => {
|
||||
if let Some(p) = self.panes.get_mut(pane_id) {
|
||||
let tab = p.remove_tab(i);
|
||||
let result = self.panes.split(
|
||||
pane_grid::Axis::Vertical,
|
||||
pane_id,
|
||||
pane::Pane::new_with_tab(tab.state),
|
||||
);
|
||||
|
||||
if let Some((pane, _)) = result {
|
||||
self.focus = Some(pane);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::Pane(pane_id, pane::Message::View(pane::ViewMessage::CloseTab(i))) => {
|
||||
if let Some(pane) = self.panes.get_mut(pane_id) {
|
||||
let _ = pane
|
||||
.update(
|
||||
pane::Message::View(pane::ViewMessage::CloseTab(i)),
|
||||
&self.config,
|
||||
)
|
||||
.map(move |msg| Message::Pane(pane_id, msg));
|
||||
if pane.tabs.is_empty() {
|
||||
self.panes.close(pane_id);
|
||||
if self.focus == Some(pane_id) {
|
||||
self.focus = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.panes.iter().any(|(_, p)| !p.tabs.is_empty()) {
|
||||
return iced::window::get_latest().and_then(iced::window::close);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::Pane(i, msg) => {
|
||||
if let Some(pane) = self.panes.get_mut(i) {
|
||||
return pane
|
||||
.update(msg, &self.config)
|
||||
.map(move |msg| Message::Pane(i, msg));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::Clicked(pane) => {
|
||||
self.focus = Some(pane);
|
||||
Task::none()
|
||||
}
|
||||
Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
|
||||
self.panes.resize(split, ratio);
|
||||
Task::none()
|
||||
}
|
||||
Message::Dragged(pane_grid::DragEvent::Dropped { pane, target }) => {
|
||||
if let pane_grid::Target::Pane(p, pane_grid::Region::Center) = target {
|
||||
let (tabs, focused_tab) = if let Some(origin) = self.panes.get_mut(pane) {
|
||||
(std::mem::take(&mut origin.tabs), origin.focused_tab)
|
||||
} else {
|
||||
(Vec::new(), 0)
|
||||
};
|
||||
|
||||
if let Some(dest) = self.panes.get_mut(p) {
|
||||
if !tabs.is_empty() {
|
||||
dest.add_tabs(tabs, focused_tab);
|
||||
}
|
||||
}
|
||||
self.panes.close(pane);
|
||||
self.focus = Some(p);
|
||||
} else {
|
||||
self.panes.drop(pane, target);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
_ => Task::none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::batch(vec![
|
||||
self.pane.subscription().map(Message::Pane),
|
||||
iced::event::listen_with(|event, status, _| match (&event, status) {
|
||||
let mut vec = vec![iced::event::listen_with(|event, status, _| {
|
||||
match (&event, status) {
|
||||
(
|
||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key: iced::keyboard::Key::Named(iced::keyboard::key::Named::Tab),
|
||||
@ -113,14 +191,49 @@ impl GUI {
|
||||
event::Status::Ignored,
|
||||
) => Some(Message::Event(event)),
|
||||
_ => None,
|
||||
}),
|
||||
])
|
||||
}
|
||||
})];
|
||||
for (id, pane) in self.panes.iter() {
|
||||
vec.push(
|
||||
pane.subscription()
|
||||
.with(*id)
|
||||
.map(|(id, msg)| Message::Pane(id, msg)),
|
||||
);
|
||||
}
|
||||
Subscription::batch(vec)
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
Column::new()
|
||||
.push(self.pane.tabs_menu_view().map(Message::Pane))
|
||||
.push(self.pane.view().map(Message::Pane))
|
||||
if self.panes.len() == 1 {
|
||||
if let Some((&id, pane)) = self.panes.iter().nth(0) {
|
||||
return Column::new()
|
||||
.push(pane.tabs_menu_view().map(move |msg| Message::Pane(id, msg)))
|
||||
.push(pane.view().map(move |msg| Message::Pane(id, msg)))
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
let focus = self.focus;
|
||||
let pane_grid = pane_grid::PaneGrid::new(&self.panes, |id, pane, _| {
|
||||
let _is_focused = focus == Some(id);
|
||||
|
||||
pane_grid::Content::new(pane.view().map(move |msg| Message::Pane(id, msg))).title_bar(
|
||||
pane_grid::TitleBar::new(
|
||||
pane.tabs_menu_view().map(move |msg| Message::Pane(id, msg)),
|
||||
),
|
||||
)
|
||||
})
|
||||
.spacing(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.on_click(Message::Clicked)
|
||||
.on_drag(Message::Dragged)
|
||||
.on_resize(10, Message::Resized);
|
||||
|
||||
Container::new(pane_grid)
|
||||
.style(liana_ui::theme::pane_grid::pane_grid_background)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use iced::{Subscription, Task};
|
||||
use iced::{Length, Subscription, Task};
|
||||
use iced_aw::ContextMenu;
|
||||
use liana_ui::{component::text::*, icon::plus_icon, theme, widget::*};
|
||||
|
||||
@ -16,14 +16,15 @@ pub enum Message {
|
||||
pub enum ViewMessage {
|
||||
FocusTab(usize),
|
||||
CloseTab(usize),
|
||||
SplitTab(usize),
|
||||
AddTab,
|
||||
}
|
||||
|
||||
pub struct Pane {
|
||||
tabs: Vec<tab::Tab>,
|
||||
pub tabs: Vec<tab::Tab>,
|
||||
|
||||
// this is an index in the tabs array
|
||||
focused_tab: usize,
|
||||
pub focused_tab: usize,
|
||||
|
||||
// used to generate tabs ids.
|
||||
tabs_created: usize,
|
||||
@ -42,6 +43,14 @@ impl Pane {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_tab(s: tab::State) -> Self {
|
||||
Self {
|
||||
tabs: vec![tab::Tab::new(1, s)],
|
||||
focused_tab: 0,
|
||||
tabs_created: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_tab(&mut self, cfg: &Config) -> Task<Message> {
|
||||
let (state, task) = tab::State::new(cfg.liana_directory.clone(), cfg.network);
|
||||
self.tabs_created += 1;
|
||||
@ -51,9 +60,13 @@ impl Pane {
|
||||
task.map(move |msg| Message::Tab(id, msg))
|
||||
}
|
||||
|
||||
fn remove_tab(&mut self, i: usize) {
|
||||
let mut tab = self.tabs.remove(i);
|
||||
fn close_tab(&mut self, i: usize) {
|
||||
let mut tab = self.remove_tab(i);
|
||||
tab.stop();
|
||||
}
|
||||
|
||||
pub fn remove_tab(&mut self, i: usize) -> tab::Tab {
|
||||
let tab = self.tabs.remove(i);
|
||||
self.focused_tab = if self.tabs.is_empty() {
|
||||
0
|
||||
} else if i < self.tabs.len() - 1 {
|
||||
@ -61,6 +74,18 @@ impl Pane {
|
||||
} else {
|
||||
self.tabs.len() - 1
|
||||
};
|
||||
tab
|
||||
}
|
||||
|
||||
pub fn add_tabs(&mut self, tabs: Vec<tab::Tab>, focused_tab: usize) {
|
||||
for tab in tabs {
|
||||
self.tabs_created += 1;
|
||||
let id = self.tabs_created;
|
||||
self.tabs.push(tab::Tab::new(id, tab.state));
|
||||
}
|
||||
if self.focused_tab + focused_tab + 1 < self.tabs.len() {
|
||||
self.focused_tab += focused_tab + 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: Message, cfg: &Config) -> Task<Message> {
|
||||
@ -79,9 +104,11 @@ impl Pane {
|
||||
}
|
||||
Message::View(ViewMessage::AddTab) => self.add_tab(cfg),
|
||||
Message::View(ViewMessage::CloseTab(i)) => {
|
||||
self.remove_tab(i);
|
||||
self.close_tab(i);
|
||||
Task::none()
|
||||
}
|
||||
// handle by the pane grid update.
|
||||
Message::View(ViewMessage::SplitTab(_)) => Task::none(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +131,7 @@ impl Pane {
|
||||
|
||||
pub fn tabs_menu_view(&self) -> Element<Message> {
|
||||
let mut menu = Row::new().spacing(3);
|
||||
let tabs_len = self.tabs.len();
|
||||
for (i, tab) in self.tabs.iter().enumerate() {
|
||||
let title = tab.title();
|
||||
menu = menu.push(ContextMenu::new(
|
||||
@ -125,10 +153,23 @@ impl Pane {
|
||||
.on_press(ViewMessage::FocusTab(i)),
|
||||
),
|
||||
move || {
|
||||
Button::new(p1_regular("Close"))
|
||||
.style(theme::button::secondary)
|
||||
.on_press(ViewMessage::CloseTab(i))
|
||||
.width(100)
|
||||
Column::new()
|
||||
.push(
|
||||
Button::new(p1_regular("Close"))
|
||||
.style(theme::button::secondary)
|
||||
.on_press(ViewMessage::CloseTab(i))
|
||||
.width(100),
|
||||
)
|
||||
.push_maybe(if tabs_len > 1 {
|
||||
Some(
|
||||
Button::new(p1_regular("Split"))
|
||||
.style(theme::button::secondary)
|
||||
.on_press(ViewMessage::SplitTab(i))
|
||||
.width(100),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.into()
|
||||
},
|
||||
));
|
||||
@ -142,11 +183,15 @@ impl Pane {
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
if let Some(t) = self.tabs.get(self.focused_tab) {
|
||||
Container::new(if let Some(t) = self.tabs.get(self.focused_tab) {
|
||||
let id = t.id;
|
||||
t.view().map(move |msg| Message::Tab(id, msg))
|
||||
} else {
|
||||
Row::new().into()
|
||||
}
|
||||
})
|
||||
.style(theme::container::background)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +47,12 @@ pub const GREEN: Color = Color::from_rgb(
|
||||
0xFF as f32 / 255.0,
|
||||
0x66 as f32 / 255.0,
|
||||
);
|
||||
pub const TRANSPARENT_GREEN: Color = Color::from_rgba(
|
||||
0x00 as f32 / 255.0,
|
||||
0xFF as f32 / 255.0,
|
||||
0x66 as f32 / 255.0,
|
||||
0.3,
|
||||
);
|
||||
pub const RED: Color = Color::from_rgb(
|
||||
0xE2 as f32 / 255.0,
|
||||
0x4E as f32 / 255.0,
|
||||
|
||||
@ -147,7 +147,7 @@ fn button(p: &Button, status: Status) -> Style {
|
||||
}
|
||||
|
||||
pub fn tab(theme: &Theme, status: Status) -> Style {
|
||||
let mut style = button(&theme.colors.buttons.secondary, status);
|
||||
let mut style = button(&theme.colors.buttons.tab, status);
|
||||
style.border.radius = 0.0.into();
|
||||
style.border.width = 0.0;
|
||||
style
|
||||
|
||||
@ -8,6 +8,7 @@ pub mod context_menu;
|
||||
pub mod notification;
|
||||
pub mod overlay;
|
||||
pub mod palette;
|
||||
pub mod pane_grid;
|
||||
pub mod pick_list;
|
||||
pub mod pill;
|
||||
pub mod progress_bar;
|
||||
|
||||
@ -16,6 +16,7 @@ pub struct Palette {
|
||||
pub sliders: Sliders,
|
||||
pub progress_bars: ProgressBars,
|
||||
pub rule: iced::Color,
|
||||
pub pane_grid: PaneGrid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
@ -44,6 +45,7 @@ pub struct Buttons {
|
||||
pub container: Button,
|
||||
pub container_border: Button,
|
||||
pub menu: Button,
|
||||
pub tab: Button,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
@ -161,6 +163,15 @@ pub struct ProgressBars {
|
||||
pub border: Option<iced::Color>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct PaneGrid {
|
||||
pub background: iced::Color,
|
||||
pub highlight_border: iced::Color,
|
||||
pub highlight_background: iced::Color,
|
||||
pub picked_split: iced::Color,
|
||||
pub hovered_split: iced::Color,
|
||||
}
|
||||
|
||||
impl std::default::Default for Palette {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -353,6 +364,28 @@ impl std::default::Default for Palette {
|
||||
border: color::TRANSPARENT.into(),
|
||||
}),
|
||||
},
|
||||
tab: Button {
|
||||
active: ButtonPalette {
|
||||
background: color::GREY_6,
|
||||
text: color::GREY_2,
|
||||
border: color::GREY_7.into(),
|
||||
},
|
||||
hovered: ButtonPalette {
|
||||
background: color::GREY_6,
|
||||
text: color::GREEN,
|
||||
border: color::GREEN.into(),
|
||||
},
|
||||
pressed: Some(ButtonPalette {
|
||||
background: color::LIGHT_BLACK,
|
||||
text: color::GREEN,
|
||||
border: color::GREEN.into(),
|
||||
}),
|
||||
disabled: Some(ButtonPalette {
|
||||
background: color::GREY_6,
|
||||
text: color::GREY_2,
|
||||
border: color::GREY_7.into(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cards: Cards {
|
||||
simple: ContainerPalette {
|
||||
@ -505,6 +538,13 @@ impl std::default::Default for Palette {
|
||||
background: color::GREY_6,
|
||||
},
|
||||
rule: color::GREY_1,
|
||||
pane_grid: PaneGrid {
|
||||
background: color::BLACK,
|
||||
highlight_border: color::GREEN,
|
||||
highlight_background: color::TRANSPARENT_GREEN,
|
||||
picked_split: color::GREEN,
|
||||
hovered_split: color::GREEN,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
liana-ui/src/theme/pane_grid.rs
Normal file
45
liana-ui/src/theme/pane_grid.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use iced::widget::container;
|
||||
use iced::widget::pane_grid::{Catalog, Highlight, Line, Style, StyleFn};
|
||||
use iced::Border;
|
||||
|
||||
use super::Theme;
|
||||
|
||||
impl Catalog for Theme {
|
||||
type Class<'a> = StyleFn<'a, Self>;
|
||||
|
||||
fn default<'a>() -> <Self as Catalog>::Class<'a> {
|
||||
Box::new(primary)
|
||||
}
|
||||
|
||||
fn style(&self, class: &<Self as Catalog>::Class<'_>) -> Style {
|
||||
class(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary(theme: &Theme) -> Style {
|
||||
Style {
|
||||
hovered_region: Highlight {
|
||||
background: theme.colors.pane_grid.highlight_background.into(),
|
||||
border: Border {
|
||||
color: theme.colors.pane_grid.highlight_border,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
},
|
||||
picked_split: Line {
|
||||
color: theme.colors.pane_grid.picked_split,
|
||||
width: 2.0,
|
||||
},
|
||||
hovered_split: Line {
|
||||
color: theme.colors.pane_grid.hovered_split,
|
||||
width: 2.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pane_grid_background(theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(theme.colors.pane_grid.background.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user