refactor(editor): restore widget boundary invariants

This commit is contained in:
Kayshen-X 2026-06-18 23:42:20 +08:00
parent c6ceca8999
commit 0ffbf4f77e
8 changed files with 132 additions and 35 deletions

View file

@ -0,0 +1,31 @@
//! Shared font-family catalog used by typography widgets and hosts.
/// TS `BUNDLED_FAMILIES` (use-system-fonts.ts:9-21).
pub const BUNDLED_FONT_FAMILIES: [&str; 11] = [
"Inter",
"Poppins",
"Roboto",
"Montserrat",
"Open Sans",
"Lato",
"Raleway",
"DM Sans",
"Playfair Display",
"Nunito",
"Source Sans 3",
];
/// TS `FALLBACK_SYSTEM_FONTS` (use-system-fonts.ts:24-36).
pub const FALLBACK_SYSTEM_FONTS: [&str; 11] = [
"Arial",
"Helvetica",
"Helvetica Neue",
"Georgia",
"Times New Roman",
"Courier New",
"Verdana",
"Trebuchet MS",
"Tahoma",
"Impact",
"Comic Sans MS",
];

View file

@ -31,6 +31,7 @@ pub use op_i18n as i18n;
pub use op_editor_core::render_backend;
pub mod accessibility;
pub mod font_catalog;
pub mod layout_scene;
pub mod layout_scene_hit;
pub mod scene_vars;

View file

@ -89,6 +89,7 @@ pub mod property_panel_visibility;
pub mod property_panel_widget;
#[cfg(test)]
mod property_panel_widget_tests;
pub mod text_input;
mod text_selection;
pub mod toolbar;
@ -223,6 +224,7 @@ pub mod variables_panel;
mod variables_preset_menu;
pub use prop_row::PropertyRow;
pub use text_input::TextInputWidget;
pub use tree::{TreeItem, TreeWidget};
pub use layer_panel::{LayerItem, LayerPanel};

View file

@ -28,36 +28,7 @@ use crate::widgets::PaintCx;
use crate::{Point2D, Rect, TextLayout};
use jian_widgets::components::select::{SelectHit, SelectState};
/// TS `BUNDLED_FAMILIES` (use-system-fonts.ts:9-21).
pub const BUNDLED_FONT_FAMILIES: [&str; 11] = [
"Inter",
"Poppins",
"Roboto",
"Montserrat",
"Open Sans",
"Lato",
"Raleway",
"DM Sans",
"Playfair Display",
"Nunito",
"Source Sans 3",
];
/// TS `FALLBACK_SYSTEM_FONTS` (use-system-fonts.ts:24-36) — shown
/// when no host enumeration is available (wasm host).
pub const FALLBACK_SYSTEM_FONTS: [&str; 11] = [
"Arial",
"Helvetica",
"Helvetica Neue",
"Georgia",
"Times New Roman",
"Courier New",
"Verdana",
"Trebuchet MS",
"Tahoma",
"Impact",
"Comic Sans MS",
];
pub use crate::font_catalog::{BUNDLED_FONT_FAMILIES, FALLBACK_SYSTEM_FONTS};
/// One selectable row (TS `FontInfo`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -0,0 +1,93 @@
//! OP widget facade for Jian's reusable single-line text input view.
use super::{LayoutBox, LayoutCx, PaintCx, Widget, WidgetId};
use crate::{Point2D, Rect};
use jian_core::text_input::TextInputState;
use jian_widgets::components::text_input::TextInputView;
use jian_widgets::Tokens;
const DEFAULT_WIDTH: f32 = 160.0;
const DEFAULT_HEIGHT: f32 = 30.0;
pub struct TextInputWidget {
pub id: WidgetId,
pub state: TextInputState,
pub placeholder: String,
pub focused: bool,
pub font_size: f32,
pub now_ms: u64,
pub pad_x: f32,
pub tokens: Tokens,
}
impl TextInputWidget {
pub fn new(id: u64, state: TextInputState) -> Self {
Self {
id: WidgetId::new(id),
state,
placeholder: String::new(),
focused: false,
font_size: 13.0,
now_ms: 0,
pad_x: 8.0,
tokens: Tokens::dark(),
}
}
pub fn with_placeholder(mut self, placeholder: impl Into<String>) -> Self {
self.placeholder = placeholder.into();
self
}
}
impl Widget for TextInputWidget {
fn id(&self) -> WidgetId {
self.id
}
fn layout(&self, cx: &LayoutCx) -> LayoutBox {
LayoutBox {
rect: Rect {
origin: Point2D::new(0.0, 0.0),
size: Point2D::new(cx.available_width.max(DEFAULT_WIDTH), DEFAULT_HEIGHT),
},
}
}
fn paint(&self, cx: &mut PaintCx<'_>, rect: Rect) {
let view = TextInputView {
state: &self.state,
placeholder: &self.placeholder,
focused: self.focused,
font_size: self.font_size,
now_ms: self.now_ms,
pad_x: self.pad_x,
};
view.paint(cx.backend, rect, &self.tokens);
}
fn access_node(&self) -> accesskit::Node {
let mut node = accesskit::Node::new(accesskit::Role::TextInput);
let label = if self.placeholder.is_empty() {
self.state.text()
} else {
&self.placeholder
};
node.set_label(label);
node
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exposes_text_input_access_role() {
let widget = TextInputWidget::new(401, TextInputState::with_text("hello"));
let node = widget.access_node();
assert_eq!(node.role(), accesskit::Role::TextInput);
}
}

View file

@ -4,9 +4,8 @@
//! (`op_editor_ui::widgets::property_panel_typography`).
use super::WidgetHostNative;
use op_editor_ui::widgets::property_panel_typography::{
font_picker_entries, BUNDLED_FONT_FAMILIES,
};
use op_editor_ui::font_catalog::BUNDLED_FONT_FAMILIES;
use op_editor_ui::widgets::property_panel_typography::font_picker_entries;
use op_editor_ui::widgets::{PropertyPanel, TOP_BAR_HEIGHT};
use op_editor_ui::{Point2D, Rect};

View file

@ -15,7 +15,7 @@ use crate::repaint_ctx::RepaintContext;
use jian_ops_schema::node::{PenNode, TextContent};
use js_sys::{Array, Function, Promise, Reflect, Uint8Array};
use op_editor_core::pen_node_ext::PenNodeExt;
use op_editor_ui::widgets::property_panel_typography::BUNDLED_FONT_FAMILIES;
use op_editor_ui::font_catalog::BUNDLED_FONT_FAMILIES;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use op_editor_ui::widgets::property_panel_typography::BUNDLED_FONT_FAMILIES;
use op_editor_ui::font_catalog::BUNDLED_FONT_FAMILIES;
use super::WidgetHost;