pub mod internal;
use crate::buf::BufferWk;
use crate::cmdltext::CmdlineTextWk;
use crate::inodify_enum_impl;
use crate::prelude::*;
use crate::ui::canvas::Canvas;
use crate::ui::canvas::CanvasArc;
use crate::ui::canvas::CursorStyle;
use crate::ui::viewport::CursorViewportArc;
use crate::ui::viewport::ViewportArc;
use crate::ui::widget::WidgetContext;
use crate::ui::widget::Widgetable;
use crate::ui::widget::cmdline::Cmdline;
use crate::ui::widget::cmdline::indicator::CmdlineIndicator;
use crate::ui::widget::cmdline::indicator::CmdlineIndicatorSymbol;
use crate::ui::widget::cmdline::input::CmdlineInput;
use crate::ui::widget::cmdline::message::CmdlineMessage;
use crate::ui::widget::cursor::Cursor;
use crate::ui::widget::panel::Panel;
use crate::ui::widget::window::Window;
use crate::ui::widget::window::content::WindowContent;
use crate::ui::widget::window::opt::WindowGlobalOptions;
use crate::ui::widget::window::opt::WindowGlobalOptionsBuilder;
use crate::ui::widget::window::opt::WindowOptions;
use crate::ui::widget::window::opt::WindowOptionsBuilder;
use crate::widgetable_enum_impl;
pub use internal::*;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::rc::Rc;
use std::sync::Arc;
use taffy::Style;
use taffy::TaffyResult;
#[derive(Debug, Clone)]
pub enum Node {
Panel(Panel),
Cursor(Cursor),
Window(Window),
WindowContent(WindowContent),
Cmdline(Cmdline),
CmdlineIndicator(CmdlineIndicator),
CmdlineInput(CmdlineInput),
CmdlineMessage(CmdlineMessage),
}
inodify_enum_impl!(
Node,
Panel,
Cursor,
Window,
WindowContent,
Cmdline,
CmdlineIndicator,
CmdlineInput,
CmdlineMessage
);
widgetable_enum_impl!(
Node,
Panel,
Cursor,
Window,
WindowContent,
Cmdline,
CmdlineIndicator,
CmdlineInput,
CmdlineMessage
);
#[derive(Clone)]
pub struct Tree {
base: Itree<Node>,
cursor_id: Option<NodeId>,
cmdline_id: Option<NodeId>,
window_ids: BTreeSet<NodeId>,
current_window_id: Option<NodeId>,
global_options: WindowGlobalOptions,
global_local_options: WindowOptions,
}
pub type TreeIter<'a> = ItreeIter<'a, Node>;
arc_mutex_ptr!(Tree);
impl Debug for Tree {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tree").field("base", &self.base).finish()
}
}
impl Tree {
pub fn new(style: Style) -> TaffyResult<Self> {
let mut base = Itree::new();
let id = {
let context = base.context();
let mut context = context.borrow_mut();
let id = context.new_leaf_default(style, "Root")?;
context.compute_layout(id)?;
id
};
let root = Panel::new(id, Rc::downgrade(&base.context()));
let root = Node::Panel(root);
base.nodes_mut().insert(id, root);
Ok(Tree {
base,
cursor_id: None,
cmdline_id: None,
window_ids: BTreeSet::new(),
current_window_id: None,
global_options: WindowGlobalOptionsBuilder::default().build().unwrap(),
global_local_options: WindowOptionsBuilder::default().build().unwrap(),
})
}
pub fn len(&self) -> usize {
self.base.len()
}
pub fn is_empty(&self) -> bool {
self.base.is_empty()
}
pub fn context(&self) -> TreeContextRc {
self.base.context()
}
pub fn root_id(&self) -> NodeId {
self.base.root_id()
}
pub fn parent_id(&self, id: NodeId) -> Option<NodeId> {
self.base.parent_id(id)
}
pub fn children_ids(&self, id: NodeId) -> Vec<NodeId> {
self.base.children_ids(id).unwrap_or_default()
}
pub fn node(&self, id: NodeId) -> Option<&Node> {
self.base.nodes().get(&id)
}
pub fn node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
self.base.nodes_mut().get_mut(&id)
}
pub fn iter(&self) -> TreeIter<'_> {
TreeIter::new(&self.base, Some(self.root_id()))
}
pub fn cursor_id(&self) -> Option<NodeId> {
self.cursor_id
}
pub fn cmdline_id(&self) -> Option<NodeId> {
self.cmdline_id
}
pub fn current_window_id(&self) -> Option<NodeId> {
self.current_window_id
}
pub fn set_current_window_id(
&mut self,
id: Option<NodeId>,
) -> Option<NodeId> {
if cfg!(debug_assertions)
&& let Some(id) = id
{
debug_assert!(self.node(id).is_some());
debug_assert!(self.window_ids.contains(&id));
debug_assert!(matches!(self.node(id).unwrap(), Node::Window(_)));
}
let old = self.current_window_id;
self.current_window_id = id;
old
}
pub fn window_ids(&self) -> &BTreeSet<NodeId> {
&self.window_ids
}
}
impl Tree {
pub fn cursor(&self) -> Option<&Cursor> {
let cursor_id = self.cursor_id?;
match self.node(cursor_id)? {
Node::Cursor(cursor) => {
debug_assert_eq!(cursor.id(), cursor_id);
Some(cursor)
}
_ => unreachable!(),
}
}
pub fn cursor_mut(&mut self) -> Option<&mut Cursor> {
let cursor_id = self.cursor_id?;
match self.node_mut(cursor_id)? {
Node::Cursor(cursor) => {
debug_assert_eq!(cursor.id(), cursor_id);
Some(cursor)
}
_ => unreachable!(),
}
}
pub fn window(&self, id: NodeId) -> Option<&Window> {
match self.node(id)? {
Node::Window(window) => {
debug_assert_eq!(window.id(), id);
Some(window)
}
_ => unreachable!(),
}
}
pub fn window_mut(&mut self, id: NodeId) -> Option<&mut Window> {
match self.node_mut(id)? {
Node::Window(window) => {
debug_assert_eq!(window.id(), id);
Some(window)
}
_ => unreachable!(),
}
}
pub fn current_window(&self) -> Option<&Window> {
let current_window_id = self.current_window_id?;
self.window(current_window_id)
}
pub fn current_window_mut(&mut self) -> Option<&mut Window> {
let current_window_id = self.current_window_id?;
self.window_mut(current_window_id)
}
pub fn cmdline(&self) -> Option<&Cmdline> {
let cmdline_id = self.cmdline_id?;
match self.node(cmdline_id)? {
Node::Cmdline(cursor) => {
debug_assert_eq!(cursor.id(), cmdline_id);
Some(cursor)
}
_ => unreachable!(),
}
}
pub fn cmdline_input(&self) -> Option<&CmdlineInput> {
let cmdline_id = self.cmdline_id?;
match self.node(cmdline_id)? {
Node::Cmdline(cmdline) => {
debug_assert_eq!(cmdline.id(), cmdline_id);
let input_id = cmdline.input_id();
match self.node(input_id)? {
Node::CmdlineInput(input) => Some(input),
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
pub fn cmdline_message(&self) -> Option<&CmdlineMessage> {
let cmdline_id = self.cmdline_id?;
match self.node(cmdline_id)? {
Node::Cmdline(cmdline) => {
debug_assert_eq!(cmdline.id(), cmdline_id);
let message_id = cmdline.message_id();
match self.node(message_id)? {
Node::CmdlineMessage(message) => Some(message),
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
pub fn cmdline_mut(&mut self) -> Option<&mut Cmdline> {
let cmdline_id = self.cmdline_id?;
match self.node_mut(cmdline_id)? {
Node::Cmdline(cmdline) => {
debug_assert_eq!(cmdline.id(), cmdline_id);
Some(cmdline)
}
_ => unreachable!(),
}
}
}
impl Tree {
fn _insert_node(&mut self, id: NodeId, node: Node) {
match &node {
Node::Cursor(c) => {
self.cursor_id = Some(c.id());
}
Node::Cmdline(c) => {
self.cmdline_id = Some(c.id());
}
Node::Window(w) => {
self.window_ids.insert(w.id());
}
_ => {}
}
self.base.nodes_mut().insert(id, node);
}
pub fn new_window_with_parent(
&mut self,
parent_id: NodeId,
style: Style,
opts: WindowOptions,
buffer: BufferWk,
) -> TaffyResult<NodeId> {
let (id, content_id, content_actual_shape) = {
let context = self.base.context();
let mut context = context.borrow_mut();
let id = context.new_with_parent_default(parent_id, style, "Window")?;
let content_style = Style {
size: taffy::Size {
width: taffy::prelude::percent(1.0_f32),
height: taffy::prelude::percent(1.0_f32),
},
..Default::default()
};
let content_id =
context.new_with_parent_default(id, content_style, "WindowContent")?;
let root_id = context.root();
context.compute_layout(root_id)?;
let content_actual_shape =
context.actual_shape(content_id).copied().unwrap();
(id, content_id, content_actual_shape)
};
let window = Window::new(
id,
Rc::downgrade(&self.context()),
opts,
buffer.clone(),
content_id,
&content_actual_shape.size(),
);
let viewport = window.viewport();
let window = Node::Window(window);
self._insert_node(id, window);
let content = WindowContent::new(
content_id,
Rc::downgrade(&self.context()),
buffer.clone(),
Arc::downgrade(&viewport),
);
let content = Node::WindowContent(content);
self._insert_node(content_id, content);
Ok(id)
}
pub fn new_cursor_with_parent(
&mut self,
parent_id: NodeId,
blinking: bool,
hidden: bool,
style: CursorStyle,
) -> TaffyResult<NodeId> {
let id = {
let context = self.base.context();
let mut context = context.borrow_mut();
let cursor_style = Style {
position: taffy::Position::Absolute,
size: taffy::Size {
width: taffy::prelude::length(1_u16),
height: taffy::prelude::length(1_u16),
},
inset: taffy::Rect {
left: taffy::prelude::length(0_u16),
top: taffy::prelude::length(0_u16),
right: taffy::prelude::auto(),
bottom: taffy::prelude::auto(),
},
..Default::default()
};
let id = context.new_with_parent(
parent_id,
cursor_style,
DEFAULT_ZINDEX,
TruncatePolicy::RESERVED,
*DEFAULT_SHAPE,
*DEFAULT_ACTUAL_SHAPE,
"Cursor",
)?;
let root_id = context.root();
context.compute_layout(root_id)?;
id
};
let cursor =
Cursor::new(id, Rc::downgrade(&self.context()), blinking, hidden, style);
let cursor = Node::Cursor(cursor);
self._insert_node(id, cursor);
Ok(id)
}
pub fn new_cmdline_with_parent(
&mut self,
parent_id: NodeId,
style: Style,
indicator_symbol: CmdlineIndicatorSymbol,
cmdline_text: CmdlineTextWk,
) -> TaffyResult<NodeId> {
let (
id,
input_panel_id,
indicator_id,
input_id,
input_actual_shape,
message_id,
message_actual_shape,
) = {
let context = self.base.context();
let mut context = context.borrow_mut();
let indicator_style = Style {
..Default::default()
};
let input_style = Style {
..Default::default()
};
let input_panel_style = Self::_cmdline_input_panel_style(false);
let message_style = Self::_cmdline_message_style(false);
trace!(
"new input_panel_style.display:{:?}, message_style.display:{:?}",
input_panel_style.display, message_style.display
);
let id = context.new_with_parent_default(parent_id, style, "Cmdline")?;
let input_panel_id = context.new_with_parent_default(
id,
input_panel_style,
"CmdlinePanel",
)?;
let indicator_id = context.new_with_parent_default(
input_panel_id,
indicator_style,
"CmdlineIndicator",
)?;
let input_id = context.new_with_parent_default(
input_panel_id,
input_style,
"CmdlineInput",
)?;
let message_id =
context.new_with_parent_default(id, message_style, "CmdlineMessage")?;
let root_id = context.root();
context.compute_layout(root_id)?;
let input_actual_shape = context.actual_shape(input_id).copied().unwrap();
let message_actual_shape =
context.actual_shape(message_id).copied().unwrap();
(
id,
input_panel_id,
indicator_id,
input_id,
input_actual_shape,
message_id,
message_actual_shape,
)
};
let cmdline = Cmdline::new(
id,
Rc::downgrade(&self.context()),
cmdline_text.clone(),
input_panel_id,
indicator_id,
input_id,
&input_actual_shape.size(),
message_id,
&message_actual_shape.size(),
);
let input_viewport = cmdline.input_viewport();
let message_viewport = cmdline.message_viewport();
let cmdline = Node::Cmdline(cmdline);
self._insert_node(id, cmdline);
let input_panel =
Panel::new(input_panel_id, Rc::downgrade(&self.context()));
let input_panel = Node::Panel(input_panel);
self._insert_node(input_panel_id, input_panel);
let indicator = CmdlineIndicator::new(
indicator_id,
Rc::downgrade(&self.context()),
indicator_symbol,
);
let indicator = Node::CmdlineIndicator(indicator);
self._insert_node(indicator_id, indicator);
let input = CmdlineInput::new(
input_id,
Rc::downgrade(&self.context()),
cmdline_text.clone(),
Arc::downgrade(&input_viewport),
);
let input = Node::CmdlineInput(input);
self._insert_node(input_id, input);
let message = CmdlineMessage::new(
message_id,
Rc::downgrade(&self.context()),
cmdline_text,
Arc::downgrade(&message_viewport),
);
let message = Node::CmdlineMessage(message);
self._insert_node(message_id, message);
Ok(id)
}
fn _cmdline_input_panel_style(show_input: bool) -> Style {
Style {
display: if show_input {
taffy::Display::Grid
} else {
taffy::Display::None
},
grid_template_columns: vec![
taffy::prelude::length(1_u16),
taffy::prelude::fr(1_u16),
],
..Default::default()
}
}
fn _cmdline_message_style(show_input: bool) -> Style {
Style {
display: if show_input {
taffy::Display::None
} else {
taffy::Display::Grid
},
..Default::default()
}
}
fn _cmdline_toggle_input(&mut self, show_input: bool) -> TaffyResult<()> {
let cmdline = self.cmdline().unwrap();
let cmdline_id = cmdline.id();
let input_panel_id = cmdline.input_panel_id();
let message_id = cmdline.message_id();
let context = self.base.context();
let mut context = context.borrow_mut();
trace!(
"before input_panel_style.display:{:?}, message_style.display:{:?}",
context.style(input_panel_id)?.display,
context.style(message_id)?.display
);
debug_assert_eq!(
context.style(input_panel_id)?.display,
if show_input {
taffy::Display::None
} else {
taffy::Display::Grid
}
);
debug_assert_eq!(
context.style(message_id)?.display,
if show_input {
taffy::Display::Grid
} else {
taffy::Display::None
}
);
trace!(
"after input_panel_style.display:{:?}, message_style.display:{:?}",
Self::_cmdline_input_panel_style(show_input).display,
Self::_cmdline_message_style(show_input).display
);
context.set_style(
input_panel_id,
Self::_cmdline_input_panel_style(show_input),
)?;
context.set_style(message_id, Self::_cmdline_message_style(show_input))?;
context.compute_layout(cmdline_id)
}
pub fn cmdline_show_message(&mut self) -> TaffyResult<()> {
self._cmdline_toggle_input(false)
}
pub fn cmdline_show_input(&mut self) -> TaffyResult<()> {
self._cmdline_toggle_input(true)
}
fn _remove_node(&mut self, id: NodeId) {
if self.cmdline_id == Some(id) {
self.cmdline_id = None;
}
self.window_ids.remove(&id);
if self.current_window_id == Some(id) {
self.current_window_id = self.window_ids.last().copied();
}
if self.cursor_id == Some(id) {
self.cursor_id = None;
}
}
}
impl Tree {
pub fn cursor_move_position_to(
&mut self,
x: isize,
y: isize,
) -> TaffyResult<()> {
let context = self.base.context();
let mut context = context.borrow_mut();
let cursor_id = self.cursor_id.unwrap();
let parent_id = context.parent(cursor_id).unwrap();
let new_shape = self
.base
.move_position_to(&context, cursor_id, x, y, TruncatePolicy::RESERVED)
.unwrap();
let new_pos: IPos = new_shape.min().into();
let mut style = context.style(cursor_id)?.clone();
style.inset = taffy::Rect {
left: taffy::prelude::length(new_pos.x() as u16),
top: taffy::prelude::length(new_pos.y() as u16),
right: taffy::prelude::auto(),
bottom: taffy::prelude::auto(),
};
context.set_style(cursor_id, style)?;
context.compute_layout(parent_id)
}
pub fn cursor_move_position_by(
&mut self,
x: isize,
y: isize,
) -> TaffyResult<()> {
let context = self.base.context();
let mut context = context.borrow_mut();
let cursor_id = self.cursor_id.unwrap();
let parent_id = context.parent(cursor_id).unwrap();
let new_shape = self
.base
.move_position_by(&context, cursor_id, x, y, TruncatePolicy::RESERVED)
.unwrap();
let new_pos: IPos = new_shape.min().into();
let mut style = context.style(cursor_id)?.clone();
style.inset = taffy::Rect {
left: taffy::prelude::length(new_pos.x() as u16),
top: taffy::prelude::length(new_pos.y() as u16),
right: taffy::prelude::auto(),
bottom: taffy::prelude::auto(),
};
context.set_style(cursor_id, style)?;
context.compute_layout(parent_id)
}
}
impl Tree {
pub fn set_cmdline_message_viewport(&mut self, viewport: ViewportArc) {
let cmdline_id = self.cmdline_id().unwrap();
match self.node_mut(cmdline_id).unwrap() {
Node::Cmdline(cmdline) => {
cmdline.set_message_viewport(viewport.clone());
let message_id = cmdline.message_id();
match self.node_mut(message_id).unwrap() {
Node::CmdlineMessage(message) => {
message.set_viewport(Arc::downgrade(&viewport))
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
pub fn set_cmdline_indicator_symbol(
&mut self,
symbol: CmdlineIndicatorSymbol,
) -> CmdlineIndicatorSymbol {
let cmdline_indicator_id = self.cmdline().unwrap().indicator_id();
match self.node_mut(cmdline_indicator_id).unwrap() {
Node::CmdlineIndicator(indicator) => {
let old = indicator.symbol();
indicator.set_symbol(symbol);
old
}
_ => unreachable!(),
}
}
}
impl Tree {
pub fn editable_viewport(&self, id: NodeId) -> ViewportArc {
match self.node(id).unwrap() {
Node::Window(window) => window.viewport(),
Node::Cmdline(cmdline) => cmdline.input_viewport(),
_ => unreachable!(),
}
}
pub fn set_editable_viewport(&mut self, id: NodeId, viewport: ViewportArc) {
match self.node_mut(id).unwrap() {
Node::Window(window) => {
window.set_viewport(viewport.clone());
let content_id = window.content_id();
match self.node_mut(content_id).unwrap() {
Node::WindowContent(content) => {
content.set_viewport(Arc::downgrade(&viewport))
}
_ => unreachable!(),
}
}
Node::Cmdline(cmdline) => {
cmdline.set_input_viewport(viewport.clone());
let input_id = cmdline.input_id();
match self.node_mut(input_id).unwrap() {
Node::CmdlineInput(input) => {
input.set_viewport(Arc::downgrade(&viewport))
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
pub fn editable_cursor_viewport(&self, id: NodeId) -> CursorViewportArc {
match self.node(id).unwrap() {
Node::Window(window) => window.cursor_viewport(),
Node::Cmdline(cmdline) => cmdline.input_cursor_viewport(),
_ => unreachable!(),
}
}
pub fn set_editable_cursor_viewport(
&mut self,
id: NodeId,
cursor_viewport: CursorViewportArc,
) {
match self.node_mut(id).unwrap() {
Node::Window(window) => {
window.set_cursor_viewport(cursor_viewport.clone())
}
Node::Cmdline(cmdline) => {
cmdline.set_input_cursor_viewport(cursor_viewport)
}
_ => unreachable!(),
}
}
pub fn editable_options(&self, id: NodeId) -> &WindowOptions {
match self.node(id).unwrap() {
Node::Window(window) => window.options(),
Node::Cmdline(cmdline) => cmdline.options(),
_ => unreachable!(),
}
}
pub fn editable_actual_shape(&self, id: NodeId) -> U16Rect {
let editable_id = match self.node(id).unwrap() {
Node::Window(window) => window.content_id(),
Node::Cmdline(cmdline) => cmdline.input_id(),
_ => unreachable!(),
};
self.node(editable_id).unwrap().actual_shape()
}
}
impl Tree {
pub fn global_options(&self) -> &WindowGlobalOptions {
&self.global_options
}
pub fn global_options_mut(&mut self) -> &mut WindowGlobalOptions {
&mut self.global_options
}
pub fn set_global_options(&mut self, options: WindowGlobalOptions) {
self.global_options = options;
}
pub fn global_local_options(&self) -> &WindowOptions {
&self.global_local_options
}
pub fn global_local_options_mut(&mut self) -> &mut WindowOptions {
&mut self.global_local_options
}
pub fn set_global_local_options(&mut self, options: WindowOptions) {
self.global_local_options = options;
}
}
impl Tree {
pub fn draw(&self, canvas: CanvasArc, context: &WidgetContext) {
let mut canvas = lock!(canvas);
for node in self.iter() {
if node.enabled() {
node.draw(&mut canvas, context);
}
}
}
}