use crate::content::TextContentsWk;
use crate::prelude::*;
use crate::ui::canvas::Canvas;
use crate::ui::tree::*;
use crate::ui::viewport::{
CursorViewport, CursorViewportArc, Viewport, ViewportArc,
};
use crate::ui::widget::cursor::Cursor;
use crate::ui::widget::window::opt::{WindowOptions, WindowOptionsBuilder};
use crate::ui::widget::{EditableWidgetable, Widgetable};
use crate::{
geo_rect_as, inode_enum_dispatcher, inode_itree_impl, widget_enum_dispatcher,
};
use indicator::{Indicator, IndicatorSymbol};
use input::Input;
use message::Message;
use root::RootContainer;
use std::sync::Arc;
pub mod indicator;
pub mod input;
pub mod message;
pub mod root;
#[cfg(test)]
pub mod indicator_tests;
#[derive(Debug, Clone)]
pub enum CommandLineNode {
RootContainer(RootContainer),
Indicator(Indicator),
Input(Input),
Cursor(Cursor),
Message(Message),
}
inode_enum_dispatcher!(
CommandLineNode,
RootContainer,
Indicator,
Input,
Cursor,
Message
);
widget_enum_dispatcher!(
CommandLineNode,
RootContainer,
Indicator,
Input,
Cursor,
Message
);
#[derive(Debug, Clone)]
pub struct CommandLine {
base: Itree<CommandLineNode>,
options: WindowOptions,
indicator_id: TreeNodeId,
input_id: TreeNodeId,
cursor_id: Option<TreeNodeId>,
message_id: TreeNodeId,
input_viewport: ViewportArc,
input_cursor_viewport: CursorViewportArc,
message_viewport: ViewportArc,
}
impl CommandLine {
pub fn new(shape: IRect, text_contents: TextContentsWk) -> Self {
let options = WindowOptionsBuilder::default()
.wrap(false)
.line_break(false)
.scroll_off(0_u16)
.build()
.unwrap();
let root = RootContainer::new(shape);
let root_id = root.id();
let root_node = CommandLineNode::RootContainer(root);
let mut base = Itree::new(root_node);
let indicator_shape =
IRect::new(shape.min().into(), (shape.min().x + 1, shape.max().y));
let indicator = Indicator::new(indicator_shape, IndicatorSymbol::Empty);
let indicator_id = indicator.id();
let mut indicator_node = CommandLineNode::Indicator(indicator);
indicator_node.set_visible(false);
base.bounded_insert(root_id, indicator_node);
let input_shape =
IRect::new((shape.min().x + 1, shape.min().y), shape.max().into());
let (input_viewport, input_cursor_viewport, message_viewport) = {
let input_actual_shape = geo_rect_as!(input_shape, u16);
let text_contents = text_contents.upgrade().unwrap();
let text_contents = lock!(text_contents);
let input_viewport = Viewport::view(
&options,
text_contents.command_line_input(),
&input_actual_shape,
0,
0,
);
let input_cursor_viewport = CursorViewport::from_top_left(
&input_viewport,
text_contents.command_line_input(),
);
let message_actual_shape = geo_rect_as!(shape, u16);
let message_viewport = Viewport::view(
&options,
text_contents.command_line_message(),
&message_actual_shape,
0,
0,
);
(input_viewport, input_cursor_viewport, message_viewport)
};
let input_viewport = Viewport::to_arc(input_viewport);
let input_cursor_viewport = CursorViewport::to_arc(input_cursor_viewport);
let message_viewport = Viewport::to_arc(message_viewport);
let input = Input::new(
input_shape,
text_contents.clone(),
Arc::downgrade(&input_viewport),
);
let input_id = input.id();
let mut input_node = CommandLineNode::Input(input);
input_node.set_visible(false);
base.bounded_insert(root_id, input_node);
let message = Message::new(
shape,
text_contents.clone(),
Arc::downgrade(&message_viewport),
);
let message_id = message.id();
let message_node = CommandLineNode::Message(message);
base.bounded_insert(root_id, message_node);
Self {
base,
options,
indicator_id,
input_id,
message_id,
cursor_id: None,
input_viewport,
input_cursor_viewport,
message_viewport,
}
}
}
inode_itree_impl!(CommandLine, base);
impl Widgetable for CommandLine {
fn draw(&self, canvas: &mut Canvas) {
for node in self.base.iter() {
if !node.visible() {
continue;
}
node.draw(canvas);
}
}
}
impl CommandLine {
pub fn options(&self) -> &WindowOptions {
&self.options
}
pub fn set_options(&mut self, options: &WindowOptions) {
self.options = *options;
}
pub fn cursor_id(&self) -> Option<TreeNodeId> {
self.cursor_id
}
pub fn indicator_id(&self) -> TreeNodeId {
self.indicator_id
}
pub fn input_id(&self) -> TreeNodeId {
self.input_id
}
pub fn message_id(&self) -> TreeNodeId {
self.message_id
}
}
impl CommandLine {
pub fn input_viewport(&self) -> ViewportArc {
self.input_viewport.clone()
}
pub fn message_viewport(&self) -> ViewportArc {
self.message_viewport.clone()
}
pub fn set_input_viewport(&mut self, viewport: ViewportArc) {
self.input_viewport = viewport.clone();
if let Some(CommandLineNode::Input(input)) =
self.base.node_mut(self.input_id)
{
input.set_viewport(Arc::downgrade(&viewport));
}
}
pub fn set_message_viewport(&mut self, viewport: ViewportArc) {
self.message_viewport = viewport.clone();
if let Some(CommandLineNode::Message(message)) =
self.base.node_mut(self.message_id)
{
message.set_viewport(Arc::downgrade(&viewport));
}
}
pub fn input_cursor_viewport(&self) -> CursorViewportArc {
self.input_cursor_viewport.clone()
}
pub fn set_input_cursor_viewport(
&mut self,
cursor_viewport: CursorViewportArc,
) {
self.input_cursor_viewport = cursor_viewport;
}
}
impl EditableWidgetable for CommandLine {
fn editable_viewport(&self) -> ViewportArc {
self.input_viewport()
}
fn set_editable_viewport(&mut self, viewport: ViewportArc) {
self.set_input_viewport(viewport);
}
fn editable_cursor_viewport(&self) -> CursorViewportArc {
self.input_cursor_viewport()
}
fn set_editable_cursor_viewport(
&mut self,
cursor_viewport: CursorViewportArc,
) {
self.set_input_cursor_viewport(cursor_viewport);
}
fn editable_options(&self) -> &WindowOptions {
self.options()
}
fn editable_actual_shape(&self) -> &U16Rect {
self.input().actual_shape()
}
fn move_editable_cursor_to(&mut self, x: isize, y: isize) -> Option<IRect> {
self.move_cursor_to(x, y)
}
fn editable_cursor_id(&self) -> Option<TreeNodeId> {
self.cursor_id()
}
}
impl CommandLine {
pub fn show_message(&mut self) {
self.indicator_mut().set_visible(false);
self.input_mut().set_visible(false);
self.message_mut().set_visible(true);
}
pub fn show_input(&mut self) {
self.indicator_mut().set_visible(true);
self.input_mut().set_visible(true);
self.message_mut().set_visible(false);
}
}
impl CommandLine {
pub fn input(&self) -> &Input {
debug_assert!(self.base.node(self.input_id).is_some());
debug_assert!(matches!(
self.base.node(self.input_id).unwrap(),
CommandLineNode::Input(_)
));
match self.base.node(self.input_id).unwrap() {
CommandLineNode::Input(w) => {
debug_assert_eq!(w.id(), self.input_id);
w
}
_ => unreachable!(),
}
}
pub fn input_mut(&mut self) -> &mut Input {
debug_assert!(self.base.node_mut(self.input_id).is_some());
debug_assert!(matches!(
self.base.node_mut(self.input_id).unwrap(),
CommandLineNode::Input(_)
));
match self.base.node_mut(self.input_id).unwrap() {
CommandLineNode::Input(w) => {
debug_assert_eq!(w.id(), self.input_id);
w
}
_ => unreachable!(),
}
}
pub fn message(&self) -> &Message {
debug_assert!(self.base.node(self.message_id).is_some());
debug_assert!(matches!(
self.base.node(self.message_id).unwrap(),
CommandLineNode::Message(_)
));
match self.base.node(self.message_id).unwrap() {
CommandLineNode::Message(w) => {
debug_assert_eq!(w.id(), self.message_id);
w
}
_ => unreachable!(),
}
}
pub fn message_mut(&mut self) -> &mut Message {
debug_assert!(self.base.node_mut(self.message_id).is_some());
debug_assert!(matches!(
self.base.node_mut(self.message_id).unwrap(),
CommandLineNode::Message(_)
));
match self.base.node_mut(self.message_id).unwrap() {
CommandLineNode::Message(w) => {
debug_assert_eq!(w.id(), self.message_id);
w
}
_ => unreachable!(),
}
}
pub fn indicator(&self) -> &Indicator {
debug_assert!(self.base.node(self.indicator_id).is_some());
debug_assert!(matches!(
self.base.node(self.indicator_id).unwrap(),
CommandLineNode::Indicator(_)
));
match self.base.node(self.indicator_id).unwrap() {
CommandLineNode::Indicator(w) => {
debug_assert_eq!(w.id(), self.indicator_id);
w
}
_ => unreachable!(),
}
}
pub fn indicator_mut(&mut self) -> &mut Indicator {
debug_assert!(self.base.node_mut(self.indicator_id).is_some());
debug_assert!(matches!(
self.base.node_mut(self.indicator_id).unwrap(),
CommandLineNode::Indicator(_)
));
match self.base.node_mut(self.indicator_id).unwrap() {
CommandLineNode::Indicator(w) => {
debug_assert_eq!(w.id(), self.indicator_id);
w
}
_ => unreachable!(),
}
}
pub fn cursor(&self) -> Option<&Cursor> {
match self.cursor_id {
Some(cursor_id) => {
debug_assert!(self.base.node(cursor_id).is_some());
debug_assert!(matches!(
self.base.node(cursor_id).unwrap(),
CommandLineNode::Cursor(_)
));
match self.base.node(cursor_id).unwrap() {
CommandLineNode::Cursor(w) => {
debug_assert_eq!(w.id(), cursor_id);
Some(w)
}
_ => unreachable!(),
}
}
None => None,
}
}
pub fn cursor_mut(&mut self) -> Option<&mut Cursor> {
match self.cursor_id {
Some(cursor_id) => {
debug_assert!(self.base.node_mut(cursor_id).is_some());
debug_assert!(matches!(
self.base.node_mut(cursor_id).unwrap(),
CommandLineNode::Cursor(_)
));
match self.base.node_mut(cursor_id).unwrap() {
CommandLineNode::Cursor(w) => {
debug_assert_eq!(w.id(), cursor_id);
Some(w)
}
_ => unreachable!(),
}
}
None => None,
}
}
}
impl CommandLine {
pub fn insert_cursor(&mut self, cursor: Cursor) -> Option<CommandLineNode> {
self.cursor_id = Some(cursor.id());
self
.base
.bounded_insert(self.input_id, CommandLineNode::Cursor(cursor))
}
pub fn remove_cursor(&mut self) -> Option<CommandLineNode> {
match self.cursor_id {
Some(cursor_id) => {
debug_assert!(self.base.node(cursor_id).is_some());
debug_assert!(self.base.parent_id(cursor_id).is_some());
debug_assert_eq!(
self.base.parent_id(cursor_id).unwrap(),
self.input_id
);
self.cursor_id = None;
let cursor_node = self.base.remove(cursor_id);
debug_assert!(cursor_node.is_some());
debug_assert!(matches!(
cursor_node.as_ref().unwrap(),
CommandLineNode::Cursor(_)
));
cursor_node
}
None => {
debug_assert!(self.cursor_id.is_none());
debug_assert!(self.base.node(self.input_id).is_some());
debug_assert!(self.base.children_ids(self.input_id).is_empty());
None
}
}
}
pub fn move_cursor_by(&mut self, x: isize, y: isize) -> Option<IRect> {
let cursor_id = self.cursor_id.unwrap();
self.base.bounded_move_by(cursor_id, x, y)
}
pub fn move_cursor_to(&mut self, x: isize, y: isize) -> Option<IRect> {
let cursor_id = self.cursor_id.unwrap();
self.base.bounded_move_to(cursor_id, x, y)
}
}