Files
ElyOffice/apps/ely-table/src/bars.rs
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

149 lines
4.7 KiB
Rust

//! The name box and the formula bar (TABLE-03).
use ely_gpui_component::{
forms::{Input, InputEvent, TextInput},
theme::{ActiveTheme as _, ControlSize},
};
use ely_lok::LokView;
use ely_office::prelude::*;
use gpui::{
AnyElement, AppContext as _, Context, Entity, InteractiveElement as _, IntoElement,
KeyDownEvent, ParentElement as _, Styled as _, Window, div,
};
use crate::book::Book;
pub struct Bars {
pub name: Entity<TextInput>,
pub formula: Entity<TextInput>,
/// Tab, address and content the bars last took.
shown: Option<(u64, String, String)>,
/// A bar the user is typing in keeps its text.
editing: Option<Bar>,
}
#[derive(Clone, Copy, PartialEq)]
enum Bar {
Name,
Formula,
}
impl Bars {
pub fn new(window: &mut Window, cx: &mut Context<Book>) -> Bars {
let (name, formula) = (
Book::text(cx, "table.name_box", &[]),
Book::text(cx, "table.formula", &[]),
);
let name = cx.new(|cx| TextInput::new(window, cx).label(name));
let formula = cx.new(|cx| TextInput::new(window, cx).label(formula));
for (input, bar) in [(&name, Bar::Name), (&formula, Bar::Formula)] {
cx.subscribe_in(
input,
window,
move |book, input, event, window, cx| match event {
InputEvent::Focus => book.bars.editing = Some(bar),
InputEvent::Blur => {
book.bars.editing = None;
book.bars.shown = None;
book.refill(cx);
}
InputEvent::Submit => {
let text = input.read(cx).text().to_string();
book.submit(bar, text, window, cx);
}
InputEvent::Changed => {}
},
)
.detach();
}
Bars {
name,
formula,
shown: None,
editing: None,
}
}
pub fn render(&self, cx: &mut Context<Book>) -> AnyElement {
div()
.flex()
.items_center()
.gap_2()
.px_2()
.py_1()
.border_b_1()
.border_color(cx.theme().colors.border)
.on_key_down(cx.listener(|book, event: &KeyDownEvent, window, cx| {
if event.keystroke.key == "escape" {
book.leave_bars(window, cx);
}
}))
.child(
div()
.id("table-name-box")
.w_32()
.child(Input::new(&self.name).size(ControlSize::Sm)),
)
.child(
div()
.id("table-formula")
.flex_1()
.child(Input::new(&self.formula).size(ControlSize::Sm)),
)
.into_any_element()
}
}
impl Book {
/// Shows the active cell, except in a bar being typed.
pub fn sync_bars(&mut self, view: &Entity<LokView>, cx: &mut Context<Self>) {
let Some(id) = self.core.active else {
return;
};
let cell = view.read(cx).cell();
let key = (id, cell.address.clone(), cell.formula.clone());
if self.bars.shown.as_ref() == Some(&key) {
return;
}
let editing = self.bars.editing;
if editing != Some(Bar::Name) {
self.bars
.name
.update(cx, |input, cx| input.set_text(key.1.clone(), cx));
}
if editing != Some(Bar::Formula) {
self.bars
.formula
.update(cx, |input, cx| input.set_text(key.2.clone(), cx));
}
self.bars.shown = Some(key);
}
/// Shows the active tab's cell again.
pub(crate) fn refill(&mut self, cx: &mut Context<Self>) {
if let Some(view) = self.active_tab().map(|tab| tab.view.clone()) {
self.sync_bars(&view, cx);
}
}
/// Enter goes to the address or enters the content.
fn submit(&mut self, bar: Bar, text: String, window: &mut Window, cx: &mut Context<Self>) {
match bar {
Bar::Name => self.run_active("cell.goto", &[&text], cx),
Bar::Formula => self.run_active("cell.enter", &[&text], cx),
}
self.leave_bars(window, cx);
}
/// Escape or Enter hands the keys back to the grid.
fn leave_bars(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.bars.editing = None;
self.bars.shown = None;
self.refill(cx);
if let Some(tab) = self.active_tab() {
let focus = tab.view.read(cx).focus_handle().clone();
window.focus(&focus, cx);
}
}
}