makepad-widgets becomes a front crate over widgets/core and widgets/families (each family a crate of its own, linked by features, so an app compiles only the families it uses); widgets/feedback is the Send feedback panel. The dev PRs (#1241-#1264) are ported to the new layout, as in the merge of dev into work. Covers work ebf442a94..2b1a41df4 for draw widgets xr code_editor: - widgets, storybook: the widget catalogue app, some eighty new widgets, a theme store with mixable style sheets, and the rule that a press belongs to - wm, widgets: the style tween starts with all eight weights, and the new style sheet takes the last number instead of macOS's - platform: the Android desk swipes at 120 Hz — offscreen render passes and framebuffers cached, SVG meshes kept, font families that complete - Button: activate from the keyboard when focused (#1241) - app_main!: only ship the fonts an app declares (#1247) - platform, widgets, wm: every Vulkan pass renders through a negative-height viewport again, so a Wayland or X11 window stands upright, and no capture - widgets: the supersampled resolve and the map's shadow mask sample their textures as stored, like every other render texture - widgets, platform: the last twelve warnings go - cef, video, platform: what Stage's browser needs from the shared layers - apps: what the CI box found - ScrollBar: add `show_handle` for a view that scrolls without a grabbable bar (#1254) - widgets: children a lookup discovers reach the dump, the snapshot and the flood searches - tests: the workspace suite, run as a whole for the first time, passes outside the example UI tests - examples: the UI tests pass, for the reasons they failed - libs: no warnings in the workspace check on any row - tests: the last three failures of the CI box's night - Linux: fix window chrome button hovers and how maximized/fullscreen windows work (#1255) - Tooltip: position anchored tooltips in the same draw (#1256) - tests: the three binaries over ten seconds in release come under it - widgets: let the host install script mods into every Splash isolate - widgets: a pooled test context forgets the last case's Escape claim - widgets: the pooled test context's resets replace the globals - RadioButton: fix its touch hover state and click-off behavior (#1258) - HtmlLink: fix its hover and pressed states (#1257) - RadioButton: take key focus on the click, not on the press (#1259) - feat(text): CoreText outline fallback for hvgl-only fonts (macOS) - draw text: the CoreText outline fallback is only ever resolved for a face whose outlines live solely in hvgl, and font-family diagnostics are the `f - platform, widgets, wm: a touch that is taken away is cancelled, never released - wm on Android: every app is its own process - widgets: touch lists scroll with Android's physics - task, cargo-makepad, widgets, audio: the task manager chooses its columns, graphs every process' network and disk traffic and installs itself as a D - widgets, draw: skeuomorphic surfaces light each other through an optional relief buffer - widgets, platform: a texture can light the relief buffer, menus take colour chips, and a style reload recompiles changed shader functions - widgets: relief surfaces can travel like mechanical keys, and dark surfaces take less neighbour light - platform, widgets, audio_route, ai: window crossfades and whole-frame presents, caption controls that click, themed menus, a stereo-pair audio tap, - platform, widgets, ai: a restyle recompiles nothing it already has, shows only complete frames, and a window can own its caption - widgets: relief surfaces can swing their light toward a point - widgets, builder: the big widget families are features, all on by default, and the Builder takes none of them - ai: Claude Desktop drives any app with the F10 panel - Constrain Splash external I/O to the host service bridge (#1243) - PortalList: stop following the end when scrolling to an earlier item (#1261) - draw: glass of a view that stopped drawing leaves the screen - widgets: the Widget lookup methods and with_script_vm_id are compiled once, not once per widget type - widgets: the library moves to makepad-widgets-core in widgets/core, and makepad-widgets becomes its front crate - widgets: the Window, widget tree and panel theme reach the tweaker, voice, AI slot and dock through hooks, not through their modules - widgets: every widget family is a crate of its own under widgets/families, and makepad-widgets links, re-exports and registers the ones its features - aichat, widgets: the chat reads the design feedback through the tweaker's hook, so linking the chat no longer needs the tweaker - apps, examples, libs: each crate builds only the widget families it uses, and the design overlay is each app's own default feature - widgets: the build scripts rerun on their own edits only, and makepad-widgets no longer reads MAKEPAD - feedback: Send feedback, a caption icon and a small panel that shows exactly what is sent - feedback: the panel's wording reads right and draws in any font - feedback: takes makepad-widgets without its default families - widgets: the empty parts of an app's caption bar drag the window again - ScrollBar: fade out when idle, like macOS overlay scrollers (#1264) - platform, draw: web builds start again - widgets: the keyboard reaches boxes and modals - widgets: a hover tooltip never sits under the mouse cursor - feedback: Send clipboard, and the dialog works from the keyboard Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1203 lines
40 KiB
Rust
1203 lines
40 KiB
Rust
pub extern crate makepad_derive_widget;
|
|
pub extern crate makepad_draw;
|
|
pub use makepad_derive_widget::*;
|
|
pub use makepad_draw::makepad_platform;
|
|
pub use makepad_draw::*;
|
|
pub use makepad_platform::log;
|
|
pub use makepad_platform::makepad_script;
|
|
pub use makepad_script::script_eval;
|
|
pub use makepad_script::{ScriptValue, ScriptVm};
|
|
|
|
pub use makepad_draw::makepad_zune_jpeg;
|
|
pub use makepad_draw::makepad_zune_png;
|
|
|
|
// Core modules (used internally first)
|
|
pub mod animator;
|
|
pub mod font_policy;
|
|
pub mod desktop_style;
|
|
pub mod app_icon;
|
|
pub mod theme_desktop_dark;
|
|
pub mod theme_desktop_light;
|
|
pub mod theme_desktop_skeleton;
|
|
pub mod theme_tokens;
|
|
pub mod widget;
|
|
pub mod widget_async;
|
|
pub mod splash_host;
|
|
pub mod splash_storage;
|
|
pub mod widget_match_event;
|
|
pub mod widget_tree;
|
|
pub mod widget_hooks;
|
|
pub mod family_api;
|
|
pub mod test_cx;
|
|
|
|
// Modules ordered to match script_mod calls
|
|
pub mod rubber_view;
|
|
pub mod scroll_bar;
|
|
pub mod scroll_bars;
|
|
pub mod scroll_motion;
|
|
pub mod view;
|
|
pub mod view_ui;
|
|
pub mod grid;
|
|
|
|
pub mod animated_image_gif;
|
|
pub mod badge;
|
|
pub mod button_group;
|
|
pub mod chip;
|
|
pub mod menu;
|
|
pub mod select;
|
|
pub mod accordion;
|
|
pub mod dialog;
|
|
pub mod toast;
|
|
pub mod breadcrumb;
|
|
pub mod button;
|
|
pub mod check_box;
|
|
pub mod icon;
|
|
pub mod image;
|
|
pub mod image_blend;
|
|
pub mod image_cache;
|
|
pub mod image_slice;
|
|
pub mod label;
|
|
pub mod link_label;
|
|
pub mod radio_button;
|
|
|
|
pub mod adaptive_view;
|
|
pub mod alert;
|
|
pub mod divider;
|
|
pub mod desktop_button;
|
|
pub mod gauss_view;
|
|
pub mod gauss_chain;
|
|
mod gauss_stack;
|
|
pub mod backdrop;
|
|
pub mod keyboard_view;
|
|
pub mod nav_control;
|
|
pub mod nav_list;
|
|
pub mod relief;
|
|
pub mod window;
|
|
pub mod cursor;
|
|
pub mod window_menu;
|
|
|
|
pub mod combo_box;
|
|
pub mod field_well;
|
|
pub mod drop_down;
|
|
pub mod drop_down2;
|
|
pub mod popup_menu;
|
|
pub mod number_field;
|
|
pub mod range_slider;
|
|
pub mod slider;
|
|
pub mod text_input;
|
|
pub mod drop_slider;
|
|
pub mod drop_toggles;
|
|
pub mod overlay_place;
|
|
pub mod tip;
|
|
pub mod popover;
|
|
pub mod value_input;
|
|
pub mod fab_controls;
|
|
pub mod menu_bar;
|
|
|
|
pub mod splitter;
|
|
|
|
pub mod fold_button;
|
|
pub mod fold_header;
|
|
|
|
pub mod loading_spinner;
|
|
pub mod progress;
|
|
pub mod playback_bar;
|
|
pub mod level_meter;
|
|
pub mod marquee;
|
|
pub mod spinner;
|
|
|
|
pub mod bare_step;
|
|
pub mod turtle_step;
|
|
|
|
pub mod rating;
|
|
pub mod tag_field;
|
|
pub mod radio_group;
|
|
pub mod kbd;
|
|
pub mod typography;
|
|
pub mod list_item;
|
|
pub mod item_selection;
|
|
pub mod avatar;
|
|
pub mod card;
|
|
pub mod media;
|
|
pub mod table;
|
|
pub mod empty_state;
|
|
pub mod timeline;
|
|
pub mod code_block;
|
|
pub mod carousel;
|
|
pub mod form;
|
|
pub mod column_picker;
|
|
pub mod picker_parts;
|
|
pub mod tree_select;
|
|
pub mod transfer;
|
|
pub mod property_inspector;
|
|
pub mod toolbar;
|
|
mod column_fit;
|
|
pub mod masonry;
|
|
pub mod tile_list;
|
|
pub mod item_grid;
|
|
pub mod kanban;
|
|
pub mod svg_select;
|
|
pub mod scroll_marks;
|
|
pub mod scroll_fade;
|
|
pub mod tour;
|
|
pub mod wheel_picker;
|
|
pub mod portal_list;
|
|
pub mod reorder_list;
|
|
|
|
pub mod cached_widget;
|
|
pub mod root;
|
|
|
|
pub mod tab;
|
|
pub mod tab_bar;
|
|
pub mod tabs;
|
|
pub mod tab_close_button;
|
|
|
|
|
|
pub mod splash;
|
|
pub mod svg;
|
|
|
|
// Touch gesture support (used by expandable_panel)
|
|
pub mod touch_gesture;
|
|
|
|
// Navigation and panels
|
|
pub mod expandable_panel;
|
|
pub mod scroll_shadow;
|
|
pub mod stack_navigation;
|
|
|
|
pub mod callout_tooltip;
|
|
pub mod floating_panel;
|
|
pub mod modal;
|
|
pub mod page_flip;
|
|
pub mod pagination;
|
|
pub mod placeholder;
|
|
pub mod hosted_view;
|
|
pub mod popup_notification;
|
|
pub mod slides_view;
|
|
pub mod tooltip;
|
|
pub mod video;
|
|
|
|
pub mod command_text_input;
|
|
pub mod defer_with_redraw;
|
|
pub mod slide_panel;
|
|
|
|
pub mod flat_list;
|
|
|
|
pub mod perf_graph;
|
|
pub mod corner_cap_view;
|
|
pub mod screen_cap;
|
|
|
|
// Commented out modules (not yet converted)
|
|
// lets depricate these for now
|
|
// pub mod toggle_panel;
|
|
// pub mod vectorline;
|
|
// pub mod web_view;
|
|
// pub mod rotated_image;
|
|
// pub mod color_picker;
|
|
// pub mod debug_view;
|
|
// pub mod performance_view;
|
|
// pub mod data_binding;
|
|
|
|
pub use crate::{
|
|
adaptive_view::*,
|
|
alert::*,
|
|
divider::*,
|
|
animated_image_gif::*,
|
|
badge::*,
|
|
breadcrumb::*,
|
|
button_group::*,
|
|
chip::*,
|
|
menu::*,
|
|
select::*,
|
|
accordion::*,
|
|
dialog::*,
|
|
toast::*,
|
|
placeholder::*,
|
|
animator::{Animate, Animator, AnimatorAction, AnimatorImpl, Play},
|
|
// loading_spinner - no public exports
|
|
bare_step::*,
|
|
button::*,
|
|
cached_widget::*,
|
|
callout_tooltip::*,
|
|
check_box::*,
|
|
combo_box::*,
|
|
field_well::*,
|
|
desktop_button::*,
|
|
|
|
drop_down::*,
|
|
drop_down2::*,
|
|
drop_toggles::*,
|
|
overlay_place::*,
|
|
popover::*,
|
|
expandable_panel::*,
|
|
flat_list::*,
|
|
|
|
fold_button::*,
|
|
fold_header::*,
|
|
gauss_view::*,
|
|
relief::*,
|
|
grid::*,
|
|
|
|
icon::*,
|
|
|
|
image::*,
|
|
image_blend::*,
|
|
image_cache::*,
|
|
image_slice::*,
|
|
keyboard_view::*,
|
|
// view_ui - no public exports
|
|
label::*,
|
|
link_label::*,
|
|
menu_bar::*,
|
|
floating_panel::*,
|
|
modal::*,
|
|
nav_control::*,
|
|
nav_list::*,
|
|
page_flip::*,
|
|
pagination::*,
|
|
hosted_view::*,
|
|
popup_menu::*,
|
|
popup_notification::*,
|
|
rating::*,
|
|
tag_field::*,
|
|
radio_group::*,
|
|
kbd::*,
|
|
list_item::*,
|
|
avatar::*,
|
|
card::*,
|
|
media::*,
|
|
table::*,
|
|
empty_state::*,
|
|
timeline::*,
|
|
code_block::*,
|
|
carousel::*,
|
|
form::*,
|
|
column_picker::*,
|
|
picker_parts::*,
|
|
tree_select::*,
|
|
transfer::*,
|
|
property_inspector::*,
|
|
toolbar::*,
|
|
masonry::*,
|
|
tile_list::*,
|
|
item_grid::*,
|
|
kanban::*,
|
|
svg_select::*,
|
|
scroll_marks::*,
|
|
scroll_fade::*,
|
|
tour::*,
|
|
wheel_picker::*,
|
|
portal_list::*,
|
|
progress::*,
|
|
playback_bar::*,
|
|
level_meter::*,
|
|
reorder_list::*,
|
|
radio_button::*,
|
|
root::*,
|
|
|
|
rubber_view::*,
|
|
// Ordered to match script_mod calls
|
|
scroll_bar::ScrollBar,
|
|
scroll_bars::{ScrollBars, ScrollExtent},
|
|
scroll_shadow::*,
|
|
slide_panel::*,
|
|
number_field::*,
|
|
range_slider::*,
|
|
slider::*,
|
|
slides_view::*,
|
|
marquee::*,
|
|
spinner::*,
|
|
|
|
splitter::*,
|
|
|
|
stack_navigation::*,
|
|
tab::*,
|
|
tab_bar::*,
|
|
tabs::*,
|
|
tab_close_button::*,
|
|
|
|
text_input::*,
|
|
tooltip::*,
|
|
// Navigation and panels
|
|
touch_gesture::*,
|
|
turtle_step::*,
|
|
|
|
view::*,
|
|
widget::{
|
|
CreateAt, DrawStateWrap, DrawStep, DrawStepApi, OptionWidgetRefExt, SnapshotPart, Widget, WidgetAction,
|
|
WidgetActionCast, WidgetActionCxExt, WidgetActionOptionApi, WidgetActionTrait,
|
|
WidgetActionsApi, WidgetFactory, WidgetNode, WidgetRef, WidgetRegister, WidgetRegistry,
|
|
WidgetSet, WidgetSetIterator, WidgetUid,
|
|
},
|
|
widget_async::{
|
|
enter_isolate, leave_isolate, set_splash_theme, set_widget_async_trace, CxSplashVmExt,
|
|
CxWidgetToScriptCallExt, IsolateEntry, ScriptAsyncCalls, ScriptAsyncId, ScriptAsyncResult,
|
|
SplashTheme, SplashVmId, MAIN_SPLASH_VM_ID,
|
|
},
|
|
widget_match_event::WidgetMatchEvent,
|
|
widget_tree::{set_ui_root, CxWidgetExt},
|
|
widget_hooks::{WidgetHooks, WindowFamilies},
|
|
|
|
window::*,
|
|
|
|
window_menu::*,
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub use crate::theme_tokens::*;
|
|
|
|
pub use crate::splash::*;
|
|
|
|
pub use crate::svg::*;
|
|
|
|
pub use crate::perf_graph::*;
|
|
pub use crate::item_selection::*;
|
|
pub use crate::corner_cap_view::*;
|
|
pub use crate::screen_cap::*;
|
|
|
|
pub use crate::video::*;
|
|
|
|
/// Which of the three themes the library is written in `theme_mod` leaves in
|
|
/// `mod.theme`. A `desktop_style` sheet is laid OVER one of these rather than
|
|
/// replacing it, so the two are separate choices.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub enum BaseTheme {
|
|
#[default]
|
|
Dark,
|
|
Light,
|
|
Skeleton,
|
|
}
|
|
|
|
impl BaseTheme {
|
|
pub const ALL: [Self; 3] = [Self::Dark, Self::Light, Self::Skeleton];
|
|
/// What a settings file or a remote surface calls it.
|
|
pub fn id(self) -> &'static str {
|
|
match self {
|
|
Self::Dark => "dark",
|
|
Self::Light => "light",
|
|
Self::Skeleton => "skeleton",
|
|
}
|
|
}
|
|
/// What a picker shows.
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
Self::Dark => "Dark",
|
|
Self::Light => "Light",
|
|
Self::Skeleton => "Skeleton",
|
|
}
|
|
}
|
|
pub fn parse(s: &str) -> Option<Self> {
|
|
Self::ALL.into_iter().find(|v| v.id() == s)
|
|
}
|
|
}
|
|
|
|
/// One value, not one per heap: every heap's `theme_mod` has to end on the
|
|
/// same base or two windows of one app disagree about what light means.
|
|
/// `desktop_style::Styles` is per-heap because a sheet is installed into the
|
|
/// heap that evaluates it; this is a preference, and nothing about it is
|
|
/// heap-shaped.
|
|
#[derive(Default)]
|
|
struct BaseThemeChoice(BaseTheme);
|
|
|
|
/// The base theme `theme_mod` will emit. `Dark` until somebody says otherwise,
|
|
/// which is exactly what the module hard-coded before there was a choice.
|
|
pub fn base_theme(cx: &mut Cx) -> BaseTheme {
|
|
cx.global::<BaseThemeChoice>().0
|
|
}
|
|
|
|
/// Choose the base theme. It is read by `theme_mod`, so it takes effect on the
|
|
/// next `script_mod` run and no sooner: a caller switching a running app
|
|
/// follows this with `cx.request_style_reload()`, which re-runs `script_mod`
|
|
/// and then re-applies the tree with `Apply::ScriptReapply` (typed text and
|
|
/// running animations survive, which `Apply::Reload` would not).
|
|
pub fn set_base_theme(cx: &mut Cx, theme: BaseTheme) {
|
|
cx.global::<BaseThemeChoice>().0 = theme;
|
|
}
|
|
|
|
/// The `widgets/` directory: the front crate `makepad-widgets`, whose
|
|
/// `themes/` and `resources/` belong to the whole library. This crate lives
|
|
/// in `widgets/core`, so it is the parent of this manifest.
|
|
pub fn widgets_dir() -> &'static str {
|
|
static DIR: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
|
DIR.get_or_init(|| {
|
|
let core = env!("CARGO_MANIFEST_DIR").trim_start_matches(r"\\?\");
|
|
std::path::Path::new(core)
|
|
.parent()
|
|
.map(|dir| dir.to_string_lossy().into_owned())
|
|
.unwrap_or_else(|| core.to_string())
|
|
})
|
|
}
|
|
|
|
/// Resources are named `makepad_widgets/resources/...` (the font policy,
|
|
/// the packagers, `crate_resource("makepad_widgets:...")`), which resolves
|
|
/// through the manifest registered for `makepad_widgets`. The script modules
|
|
/// that register one by being evaluated are all in `makepad_widgets_core`
|
|
/// and the family crates now, so the name is registered here, before the
|
|
/// theme's fonts ask for it.
|
|
fn register_widgets_resource_crate(vm: &mut ScriptVm) {
|
|
vm.bx
|
|
.code
|
|
.crate_manifests
|
|
.borrow_mut()
|
|
.insert("makepad_widgets".to_string(), widgets_dir().to_string());
|
|
}
|
|
|
|
pub fn theme_mod(vm: &mut ScriptVm) {
|
|
register_widgets_resource_crate(vm);
|
|
makepad_draw::script_mod(vm);
|
|
if !vm.is_reload() {
|
|
makepad_platform::ime::script_mod(vm);
|
|
}
|
|
|
|
vm.bx.heap.new_module(id!(prelude));
|
|
vm.bx.heap.new_module(id!(themes));
|
|
crate::animator::script_mod(vm);
|
|
crate::theme_desktop_dark::script_mod(vm);
|
|
crate::theme_desktop_light::script_mod(vm);
|
|
crate::theme_desktop_skeleton::script_mod(vm);
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
script_eval!(vm, {
|
|
mod.helper = {
|
|
startup: |v|{
|
|
mod.res.load_all_resources()
|
|
//mod.gc.set_static(mod.prelude.widgets_header);
|
|
//mod.gc.set_static(mod.prelude.widgets_internal);
|
|
//mod.gc.set_static(mod.prelude.widgets);
|
|
v
|
|
}
|
|
}
|
|
});
|
|
#[cfg(target_arch = "wasm32")]
|
|
script_eval!(vm, {
|
|
mod.helper = {
|
|
startup: |v|{
|
|
v
|
|
}
|
|
}
|
|
});
|
|
crate::font_policy::install_theme_fonts(vm);
|
|
script_eval!(vm, {
|
|
mod.prelude.widgets_header = {
|
|
..mod.res,
|
|
..mod.helper,
|
|
..mod.std,
|
|
..mod.pod,
|
|
..mod.math,
|
|
..mod.sdf,
|
|
..mod.animator,
|
|
..mod.turtle,
|
|
..mod.text,
|
|
..mod.ime,
|
|
..mod.shader,
|
|
..mod.animator.Play,
|
|
..mod.animator.Ease,
|
|
draw:mod.draw,
|
|
MouseCursor:mod.draw.MouseCursor
|
|
}
|
|
});
|
|
// The base theme, last, so everything after it reads the one that was
|
|
// chosen. `Dark` is the default, so an app that never calls
|
|
// `set_base_theme` gets precisely what this used to say outright.
|
|
match base_theme(vm.cx_mut()) {
|
|
BaseTheme::Dark => {
|
|
script_eval!(vm, {
|
|
mod.theme = mod.themes.dark
|
|
});
|
|
}
|
|
BaseTheme::Light => {
|
|
script_eval!(vm, {
|
|
mod.theme = mod.themes.light
|
|
});
|
|
}
|
|
BaseTheme::Skeleton => {
|
|
script_eval!(vm, {
|
|
mod.theme = mod.themes.skeleton
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The core's widgets alone. `makepad_widgets::widgets_mod` is the one an
|
|
/// app calls: it passes the families its features picked.
|
|
pub fn widgets_mod(vm: &mut ScriptVm) {
|
|
widgets_mod_with(vm, WindowFamilies::default(), &[]);
|
|
}
|
|
|
|
/// The core's widgets, `window`'s families where the Window names them, and
|
|
/// then `families` in order, before the prelude is made from `mod.widgets`.
|
|
pub fn widgets_mod_with(
|
|
vm: &mut ScriptVm,
|
|
window: WindowFamilies,
|
|
families: &[fn(&mut ScriptVm)],
|
|
) {
|
|
let host_io_only = vm.cx().script_data.std.host_io_only();
|
|
widgets_mod_with_io(vm, window, families, host_io_only);
|
|
}
|
|
|
|
/// The core's widgets for a Splash isolate being built: `host_io_only` is
|
|
/// the restriction it will run under (its std is restricted only after this).
|
|
pub(crate) fn widgets_mod_with_host_io(vm: &mut ScriptVm, host_io_only: bool) {
|
|
widgets_mod_with_io(vm, WindowFamilies::default(), &[], host_io_only);
|
|
}
|
|
|
|
/// With `host_io_only` (a guest held to the host service bridge) the modules
|
|
/// that reach past it stay out: the window and its menu bar (a guest must not
|
|
/// replace the app's), screen capture, and cached widgets (their singletons
|
|
/// would reach widgets the host or other isolates cached).
|
|
fn widgets_mod_with_io(
|
|
vm: &mut ScriptVm,
|
|
window: WindowFamilies,
|
|
families: &[fn(&mut ScriptVm)],
|
|
host_io_only: bool,
|
|
) {
|
|
crate::desktop_style::apply_theme(vm);
|
|
// make the prelude for our own widgets
|
|
script_eval!(vm, {
|
|
mod.prelude.widgets_internal = {
|
|
..mod.prelude.widgets_header,
|
|
theme:mod.theme,
|
|
}
|
|
});
|
|
|
|
vm.bx.heap.new_module(id!(widgets));
|
|
|
|
crate::scroll_bar::script_mod(vm);
|
|
crate::scroll_bars::script_mod(vm);
|
|
crate::view::script_mod(vm);
|
|
crate::view_ui::script_mod(vm);
|
|
crate::relief::script_mod(vm);
|
|
crate::grid::script_mod(vm);
|
|
crate::rubber_view::script_mod(vm);
|
|
|
|
crate::label::script_mod(vm);
|
|
crate::link_label::script_mod(vm);
|
|
crate::button::script_mod(vm);
|
|
crate::alert::script_mod(vm);
|
|
crate::divider::script_mod(vm);
|
|
crate::check_box::script_mod(vm);
|
|
crate::radio_button::script_mod(vm);
|
|
crate::image::script_mod(vm);
|
|
crate::animated_image_gif::script_mod(vm);
|
|
crate::image_blend::script_mod(vm);
|
|
crate::icon::script_mod(vm);
|
|
|
|
crate::adaptive_view::script_mod(vm);
|
|
crate::desktop_button::script_mod(vm);
|
|
crate::keyboard_view::script_mod(vm);
|
|
// Every Window names a `VoiceWave`, a `Tweaker` and an `AiChatSlot`,
|
|
// so their families register here; without one the name is an empty
|
|
// hidden view.
|
|
match window.voice {
|
|
Some(register) => register(vm),
|
|
None => {
|
|
script_eval!(vm, {
|
|
use mod.widgets.View
|
|
mod.widgets.VoiceWave = mod.widgets.View {
|
|
visible: false
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if !host_io_only { crate::window_menu::script_mod(vm); }
|
|
crate::nav_control::script_mod(vm);
|
|
match window.tweaker {
|
|
Some(register) => register(vm),
|
|
None => {
|
|
script_eval!(vm, {
|
|
use mod.widgets.View
|
|
mod.widgets.Tweaker = mod.widgets.View {
|
|
visible: false
|
|
}
|
|
});
|
|
}
|
|
}
|
|
crate::gauss_view::script_mod(vm);
|
|
if !host_io_only { crate::screen_cap::script_mod(vm); }
|
|
// The AI slot before the window: its DSL names `AiChatSlot`.
|
|
match window.ai {
|
|
Some(register) => register(vm),
|
|
None => {
|
|
script_eval!(vm, {
|
|
use mod.widgets.View
|
|
mod.widgets.AiChatSlot = mod.widgets.View {
|
|
visible: false
|
|
}
|
|
});
|
|
}
|
|
}
|
|
crate::app_icon::script_mod(vm);
|
|
crate::cursor::script_mod(vm);
|
|
if !host_io_only { crate::window::script_mod(vm); }
|
|
|
|
crate::popup_menu::script_mod(vm);
|
|
crate::drop_down::script_mod(vm);
|
|
crate::drop_down2::script_mod(vm);
|
|
crate::text_input::script_mod(vm);
|
|
crate::slider::script_mod(vm);
|
|
crate::range_slider::script_mod(vm);
|
|
crate::drop_slider::script_mod(vm);
|
|
crate::drop_toggles::script_mod(vm);
|
|
// The badge first: it owns the role palette and the intent names, and
|
|
// the tooltip, the chip and the segmented control all read them.
|
|
crate::badge::script_mod(vm);
|
|
crate::tip::script_mod(vm);
|
|
crate::popover::script_mod(vm);
|
|
crate::value_input::script_mod(vm);
|
|
crate::fab_controls::script_mod(vm);
|
|
crate::menu_bar::script_mod(vm);
|
|
crate::combo_box::script_mod(vm);
|
|
crate::field_well::script_mod(vm);
|
|
crate::number_field::script_mod(vm);
|
|
|
|
crate::splitter::script_mod(vm);
|
|
|
|
crate::fold_button::script_mod(vm);
|
|
crate::fold_header::script_mod(vm);
|
|
crate::accordion::script_mod(vm);
|
|
|
|
crate::loading_spinner::script_mod(vm);
|
|
crate::progress::script_mod(vm);
|
|
crate::playback_bar::script_mod(vm);
|
|
crate::level_meter::script_mod(vm);
|
|
crate::breadcrumb::script_mod(vm);
|
|
crate::pagination::script_mod(vm);
|
|
crate::nav_list::script_mod(vm);
|
|
crate::floating_panel::script_mod(vm);
|
|
crate::marquee::script_mod(vm);
|
|
crate::spinner::script_mod(vm);
|
|
crate::chip::script_mod(vm);
|
|
// The menu first: the group's split and menu buttons carry a
|
|
// `MenuPlace`, and a block's `use` only sees what already exists.
|
|
crate::menu::script_mod(vm);
|
|
crate::button_group::script_mod(vm);
|
|
crate::select::script_mod(vm);
|
|
crate::toast::script_mod(vm);
|
|
crate::placeholder::script_mod(vm);
|
|
|
|
crate::bare_step::script_mod(vm);
|
|
crate::turtle_step::script_mod(vm);
|
|
|
|
crate::portal_list::script_mod(vm);
|
|
crate::rating::script_mod(vm);
|
|
crate::tag_field::script_mod(vm);
|
|
crate::radio_group::script_mod(vm);
|
|
crate::kbd::script_mod(vm);
|
|
crate::wheel_picker::script_mod(vm);
|
|
crate::typography::script_mod(vm);
|
|
crate::list_item::script_mod(vm);
|
|
crate::avatar::script_mod(vm);
|
|
crate::card::script_mod(vm);
|
|
crate::media::script_mod(vm);
|
|
crate::table::script_mod(vm);
|
|
crate::empty_state::script_mod(vm);
|
|
crate::timeline::script_mod(vm);
|
|
crate::code_block::script_mod(vm);
|
|
crate::carousel::script_mod(vm);
|
|
crate::form::script_mod(vm);
|
|
// Before the three that draw with its panel and row surfaces.
|
|
crate::picker_parts::script_mod(vm);
|
|
crate::column_picker::script_mod(vm);
|
|
crate::tree_select::script_mod(vm);
|
|
crate::transfer::script_mod(vm);
|
|
crate::property_inspector::script_mod(vm);
|
|
crate::tour::script_mod(vm);
|
|
crate::toolbar::script_mod(vm);
|
|
crate::masonry::script_mod(vm);
|
|
crate::tile_list::script_mod(vm);
|
|
crate::item_grid::script_mod(vm);
|
|
crate::kanban::script_mod(vm);
|
|
crate::scroll_marks::script_mod(vm);
|
|
crate::scroll_fade::script_mod(vm);
|
|
crate::svg_select::script_mod(vm);
|
|
crate::reorder_list::script_mod(vm);
|
|
|
|
if !host_io_only { crate::cached_widget::script_mod(vm); }
|
|
crate::root::script_mod(vm);
|
|
|
|
crate::tab_close_button::script_mod(vm);
|
|
crate::tab::script_mod(vm);
|
|
crate::tab_bar::script_mod(vm);
|
|
crate::tabs::script_mod(vm);
|
|
|
|
// Navigation and panels
|
|
crate::scroll_shadow::script_mod(vm);
|
|
crate::stack_navigation::script_mod(vm);
|
|
crate::expandable_panel::script_mod(vm);
|
|
crate::modal::script_mod(vm);
|
|
crate::dialog::script_mod(vm);
|
|
crate::tooltip::script_mod(vm);
|
|
crate::callout_tooltip::script_mod(vm);
|
|
crate::popup_notification::script_mod(vm);
|
|
crate::video::script_mod(vm);
|
|
crate::page_flip::script_mod(vm);
|
|
crate::hosted_view::script_mod(vm);
|
|
crate::flat_list::script_mod(vm);
|
|
crate::slides_view::script_mod(vm);
|
|
crate::slide_panel::script_mod(vm);
|
|
|
|
|
|
crate::splash::script_mod(vm);
|
|
crate::svg::script_mod(vm);
|
|
crate::perf_graph::script_mod(vm);
|
|
crate::corner_cap_view::script_mod(vm);
|
|
|
|
// The families, after every core widget they may build on.
|
|
for register in families {
|
|
register(vm);
|
|
}
|
|
|
|
// Safe area inset values (in Makepad layout points). Populated from the platform's
|
|
// display_context which is set before Startup on iOS/Android. On desktop
|
|
// platforms these remain 0.0. Updated at runtime on WindowGeomChange events.
|
|
{
|
|
use makepad_script::trap::NoTrap;
|
|
let insets = vm.cx().display_context.safe_area_insets;
|
|
let widgets = vm.module(id!(widgets));
|
|
vm.bx.heap.set_value(
|
|
widgets,
|
|
id!(SAFE_INSET_PAD_TOP).into(),
|
|
insets.top.into(),
|
|
NoTrap,
|
|
);
|
|
vm.bx.heap.set_value(
|
|
widgets,
|
|
id!(SAFE_INSET_PAD_BOTTOM).into(),
|
|
insets.bottom.into(),
|
|
NoTrap,
|
|
);
|
|
vm.bx.heap.set_value(
|
|
widgets,
|
|
id!(SAFE_INSET_PAD_LEFT).into(),
|
|
insets.left.into(),
|
|
NoTrap,
|
|
);
|
|
vm.bx.heap.set_value(
|
|
widgets,
|
|
id!(SAFE_INSET_PAD_RIGHT).into(),
|
|
insets.right.into(),
|
|
NoTrap,
|
|
);
|
|
}
|
|
|
|
script_eval!(vm, {
|
|
mod.prelude.widgets = {
|
|
..mod.prelude.widgets_header,
|
|
theme:mod.theme,
|
|
..mod.widgets,
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn script_mod(vm: &mut ScriptVm) {
|
|
script_mod_with(vm, WindowFamilies::default(), &[]);
|
|
}
|
|
|
|
/// The theme, the widgets with the given families, and the desktop style
|
|
/// laid over them: what `makepad_widgets::script_mod` runs.
|
|
pub fn script_mod_with(
|
|
vm: &mut ScriptVm,
|
|
window: WindowFamilies,
|
|
families: &[fn(&mut ScriptVm)],
|
|
) {
|
|
makepad_platform::startup_trace("widgets: theme_mod begin");
|
|
theme_mod(vm);
|
|
makepad_platform::startup_trace("widgets: theme_mod done");
|
|
widgets_mod_with(vm, window, families);
|
|
crate::desktop_style::apply_widgets(vm);
|
|
makepad_platform::startup_trace("widgets: widgets_mod done");
|
|
}
|
|
|
|
// The pooled test contexts (test_cx.rs), registered with the core alone.
|
|
#[cfg(test)]
|
|
pub(crate) use crate::test_cx::PooledCx;
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn checkout_test_cx() -> PooledCx {
|
|
crate::test_cx::checkout_test_cx(script_mod)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn on_test_cx(f: impl FnOnce() + Send + 'static) {
|
|
crate::test_cx::on_test_cx(f)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod base_theme_tests {
|
|
|
|
use super::*;
|
|
|
|
/// The base theme is a choice now, and `dark` is still what an app that
|
|
/// never makes one gets.
|
|
#[test]
|
|
fn theme_mod_emits_the_chosen_base_and_still_defaults_to_dark() {
|
|
fn bg(vm: &mut ScriptVm) -> Option<u32> {
|
|
let theme = vm.module(id!(theme));
|
|
vm.bx
|
|
.heap
|
|
.value(theme, id!(color_bg_app).into(), NoTrap)
|
|
.as_color()
|
|
}
|
|
let mut cx = Cx::new(Box::new(|_, _| {}));
|
|
cx.with_vm(|vm| {
|
|
crate::script_mod(vm);
|
|
let dark = bg(vm);
|
|
assert!(dark.is_some());
|
|
assert_eq!(base_theme(vm.cx_mut()), BaseTheme::Dark);
|
|
|
|
set_base_theme(vm.cx_mut(), BaseTheme::Light);
|
|
vm.with_reload(crate::script_mod);
|
|
let light = bg(vm);
|
|
assert!(light.is_some() && light != dark, "{light:?} vs {dark:?}");
|
|
assert!(vm.take_errors().is_empty());
|
|
|
|
set_base_theme(vm.cx_mut(), BaseTheme::Skeleton);
|
|
vm.with_reload(crate::script_mod);
|
|
let skeleton = bg(vm);
|
|
assert!(skeleton.is_some());
|
|
assert!(vm.take_errors().is_empty());
|
|
|
|
// And back: the choice is a setting, not a one-way door.
|
|
set_base_theme(vm.cx_mut(), BaseTheme::Dark);
|
|
vm.with_reload(crate::script_mod);
|
|
assert_eq!(bg(vm), dark);
|
|
assert!(vm.take_errors().is_empty());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn every_base_theme_has_a_name_and_parses_back() {
|
|
for base in BaseTheme::ALL {
|
|
assert_eq!(BaseTheme::parse(base.id()), Some(base));
|
|
assert!(!base.label().is_empty());
|
|
}
|
|
assert_eq!(BaseTheme::parse("omarchy"), None);
|
|
assert_eq!(BaseTheme::default(), BaseTheme::Dark);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod animated_image_gif_registration_tests {
|
|
#[test]
|
|
fn test_animated_image_gif_is_registered_separately_from_image() {
|
|
let lib = include_str!("lib.rs");
|
|
let gif = include_str!("animated_image_gif.rs");
|
|
assert!(lib.contains("pub mod animated_image_gif;"));
|
|
assert!(lib.contains("animated_image_gif::*"));
|
|
assert!(lib.contains("crate::animated_image_gif::script_mod(vm);"));
|
|
assert!(gif.contains(
|
|
"mod.widgets.AnimatedImageGifBase = #(AnimatedImageGif::register_widget(vm))"
|
|
));
|
|
assert!(gif.contains("mod.widgets.AnimatedImageGif = set_type_default()"));
|
|
assert!(lib.contains("pub mod image;"));
|
|
assert!(lib.contains("crate::image::script_mod(vm);"));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod progress_registration_tests {
|
|
/// The progress family registers as one module, after the bases it
|
|
/// draws with and its `Intent` enum splatted into `mod.widgets`; the
|
|
/// loading spinner it sits beside stays a separate module.
|
|
#[test]
|
|
fn test_progress_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let progress = include_str!("progress.rs");
|
|
assert!(lib.contains("pub mod progress;"));
|
|
assert!(lib.contains("progress::*"));
|
|
let at = lib.find("crate::progress::script_mod(vm);").expect("progress registered");
|
|
for base in ["crate::view::script_mod(vm);", "crate::label::script_mod(vm);"] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before progress");
|
|
}
|
|
assert!(lib.contains("crate::loading_spinner::script_mod(vm);"));
|
|
assert!(progress.contains("mod.widgets.ProgressBarBase = #(ProgressBar::register_widget(vm))"));
|
|
assert!(progress.contains("mod.widgets.ProgressBarFlat = set_type_default()"));
|
|
assert!(progress.contains("mod.widgets.splat(mod.widgets.Intent)"));
|
|
assert_eq!(progress.matches("set_type_default() do mod.widgets.ProgressBarBase").count(), 1);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod button_group_registration_tests {
|
|
/// The group and the segmented control register after the button and
|
|
/// chip modules they build on, each with one type default at the flat
|
|
/// rung of the ladder.
|
|
#[test]
|
|
fn test_button_group_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let group = include_str!("button_group.rs");
|
|
assert!(lib.contains("pub mod button_group;"));
|
|
assert!(lib.contains("button_group::*"));
|
|
let at = lib.find("crate::button_group::script_mod(vm);").expect("registered");
|
|
for base in [
|
|
"crate::view::script_mod(vm);",
|
|
"crate::button::script_mod(vm);",
|
|
"crate::chip::script_mod(vm);",
|
|
"crate::menu::script_mod(vm);",
|
|
] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before the group");
|
|
}
|
|
assert!(group.contains("mod.widgets.ButtonGroupBase = #(ButtonGroup::register_widget(vm))"));
|
|
assert!(group.contains("mod.widgets.SegmentedControlBase = #(SegmentedControl::register_widget(vm))"));
|
|
assert!(group.contains("mod.widgets.SegmentedControlFlat = set_type_default()"));
|
|
assert!(group.contains("mod.widgets.SegmentedControl = mod.widgets.SegmentedControlFlat{"));
|
|
assert_eq!(group.matches("set_type_default() do mod.widgets.ButtonGroupBase").count(), 1);
|
|
assert_eq!(group.matches("set_type_default() do mod.widgets.SegmentedControlBase").count(), 1);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod dialog_registration_tests {
|
|
/// The dialog registers after the modal it is built on and the button
|
|
/// and label its chrome uses, with one type default and the presets
|
|
/// hanging off it.
|
|
#[test]
|
|
fn test_dialog_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let dialog = include_str!("dialog.rs");
|
|
assert!(lib.contains("pub mod dialog;"));
|
|
assert!(lib.contains("dialog::*"));
|
|
let at = lib.find("crate::dialog::script_mod(vm);").expect("dialog registered");
|
|
for base in [
|
|
"crate::modal::script_mod(vm);",
|
|
"crate::button::script_mod(vm);",
|
|
"crate::label::script_mod(vm);",
|
|
] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before the dialog");
|
|
}
|
|
assert!(dialog.contains("mod.widgets.DialogBase = #(Dialog::register_widget(vm))"));
|
|
assert!(dialog.contains("mod.widgets.Dialog = set_type_default()"));
|
|
assert!(dialog.contains("mod.widgets.AlertDialog = mod.widgets.Dialog{"));
|
|
assert!(dialog.contains("mod.widgets.ConfirmDialog = mod.widgets.Dialog{"));
|
|
assert_eq!(dialog.matches("set_type_default() do mod.widgets.DialogBase").count(), 1);
|
|
// The drawer and the two sheets are the dialog on an edge: presets
|
|
// of it, with no type and so no type default of their own.
|
|
assert!(dialog.contains("mod.widgets.Drawer = mod.widgets.Dialog{"));
|
|
assert!(dialog.contains("mod.widgets.SideSheet = mod.widgets.Drawer{"));
|
|
assert!(dialog.contains("mod.widgets.BottomSheet = mod.widgets.Drawer{"));
|
|
// The widget derive takes any field type beginning with "Draw" for
|
|
// a shader layer, which is why the side is not called DrawerSide.
|
|
assert!(!dialog.contains("pub enum DrawerSide"));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod chip_registration_tests {
|
|
/// The chip registers after the view, label and button modules whose
|
|
/// shapes it borrows, and after the badge whose role palette it shares,
|
|
/// with one type default at the flat rung of the ladder.
|
|
#[test]
|
|
fn test_chip_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let chip = include_str!("chip.rs");
|
|
assert!(lib.contains("pub mod chip;"));
|
|
assert!(lib.contains("chip::*"));
|
|
let at = lib.find("crate::chip::script_mod(vm);").expect("chip registered");
|
|
for base in [
|
|
"crate::view::script_mod(vm);",
|
|
"crate::label::script_mod(vm);",
|
|
"crate::button::script_mod(vm);",
|
|
"crate::badge::script_mod(vm);",
|
|
] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before chip");
|
|
}
|
|
assert!(chip.contains("mod.widgets.ChipBase = #(Chip::register_widget(vm))"));
|
|
assert!(chip.contains("mod.widgets.ChipFlat = set_type_default()"));
|
|
assert!(chip.contains("mod.widgets.Chip = mod.widgets.ChipFlat{"));
|
|
assert!(chip.contains("mod.widgets.Tag = mod.widgets.ChipFlat{"));
|
|
assert_eq!(chip.matches("set_type_default() do mod.widgets.ChipBase").count(), 1);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod nav_list_registration_tests {
|
|
/// The nav list registers after the radio button its rows must be
|
|
/// shaped like, and carries exactly one preset plus the two flows.
|
|
#[test]
|
|
fn test_nav_list_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let nav = include_str!("nav_list.rs");
|
|
assert!(lib.contains("pub mod nav_list;"));
|
|
assert!(lib.contains("nav_list::*"));
|
|
let at = lib.find("crate::nav_list::script_mod(vm);").expect("nav_list registered");
|
|
for base in ["crate::view::script_mod(vm);", "crate::radio_button::script_mod(vm);"] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before nav_list");
|
|
}
|
|
assert!(nav.contains("mod.widgets.NavListBase = #(NavList::register_widget(vm))"));
|
|
assert_eq!(nav.matches("set_type_default() do mod.widgets.NavListBase").count(), 1);
|
|
// A rail and a bar are presets over the one list, not two widgets.
|
|
assert!(nav.contains("mod.widgets.NavRail = mod.widgets.NavList{"));
|
|
assert!(nav.contains("mod.widgets.NavBar = mod.widgets.NavList{"));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod pagination_registration_tests {
|
|
/// The strip registers after the view and label it draws with, carries
|
|
/// exactly one preset, and keeps its arithmetic a free function: the
|
|
/// window is what the tests are about, the drawing is not.
|
|
#[test]
|
|
fn test_pagination_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let pagination = include_str!("pagination.rs");
|
|
assert!(lib.contains("pub mod pagination;"));
|
|
assert!(lib.contains("pagination::*"));
|
|
let at = lib.find("crate::pagination::script_mod(vm);").expect("pagination registered");
|
|
for base in ["crate::view::script_mod(vm);", "crate::label::script_mod(vm);"] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before pagination");
|
|
}
|
|
assert!(pagination.contains("mod.widgets.PaginationBase = #(Pagination::register_widget(vm))"));
|
|
assert_eq!(
|
|
pagination.matches("set_type_default() do mod.widgets.PaginationBase").count(),
|
|
1
|
|
);
|
|
assert!(pagination.contains("pub fn page_window("), "the arithmetic stays a free function");
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod breadcrumb_registration_tests {
|
|
/// The trail registers after the view and label it draws with, and
|
|
/// carries exactly one preset.
|
|
#[test]
|
|
fn test_breadcrumb_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let breadcrumb = include_str!("breadcrumb.rs");
|
|
assert!(lib.contains("pub mod breadcrumb;"));
|
|
assert!(lib.contains("breadcrumb::*"));
|
|
let at = lib.find("crate::breadcrumb::script_mod(vm);").expect("breadcrumb registered");
|
|
for base in ["crate::view::script_mod(vm);", "crate::label::script_mod(vm);"] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before breadcrumb");
|
|
}
|
|
assert!(breadcrumb.contains("mod.widgets.BreadcrumbBase = #(Breadcrumb::register_widget(vm))"));
|
|
assert_eq!(
|
|
breadcrumb.matches("set_type_default() do mod.widgets.BreadcrumbBase").count(),
|
|
1
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod marquee_registration_tests {
|
|
/// The marquee registers after the view it draws inside and carries
|
|
/// exactly one preset. It draws text and no children on purpose, so it
|
|
/// must not grow a `#[deref] view` and start redrawing child widgets
|
|
/// several times a frame — see the module's own reasoning.
|
|
#[test]
|
|
fn test_marquee_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let marquee = include_str!("marquee.rs");
|
|
assert!(lib.contains("pub mod marquee;"));
|
|
assert!(lib.contains("marquee::*"));
|
|
let at = lib.find("crate::marquee::script_mod(vm);").expect("marquee registered");
|
|
for base in ["crate::view::script_mod(vm);", "crate::label::script_mod(vm);"] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before marquee");
|
|
}
|
|
assert!(marquee.contains("mod.widgets.MarqueeBase = #(Marquee::register_widget(vm))"));
|
|
assert_eq!(marquee.matches("set_type_default() do mod.widgets.MarqueeBase").count(), 1);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod spinner_registration_tests {
|
|
/// The spinner family registers after the button, label, view and
|
|
/// glass modules it composes, and leaves `loading_spinner` untouched:
|
|
/// that DSL-only view has shader parameters seven apps override by name.
|
|
#[test]
|
|
fn test_spinner_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let spinner = include_str!("spinner.rs");
|
|
assert!(lib.contains("pub mod spinner;"));
|
|
assert!(lib.contains("spinner::*"));
|
|
let at = lib.find("crate::spinner::script_mod(vm);").expect("spinner registered");
|
|
for base in [
|
|
"crate::view::script_mod(vm);",
|
|
"crate::label::script_mod(vm);",
|
|
"crate::button::script_mod(vm);",
|
|
"crate::gauss_view::script_mod(vm);",
|
|
"crate::loading_spinner::script_mod(vm);",
|
|
] {
|
|
assert!(lib.find(base).expect(base) < at, "{base} must register before spinner");
|
|
}
|
|
assert!(spinner.contains("mod.widgets.SpinnerBase = #(Spinner::register_widget(vm))"));
|
|
assert!(spinner.contains("mod.widgets.SpinnerFlat = set_type_default()"));
|
|
assert_eq!(spinner.matches("set_type_default() do mod.widgets.SpinnerBase").count(), 1);
|
|
assert!(!spinner.contains("mod.widgets.LoadingSpinner"));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod popover_registration_tests {
|
|
/// The popover registers its base, its flat default and its bevelled
|
|
/// variant, after the view and tip it builds on.
|
|
#[test]
|
|
fn test_popover_is_registered_after_its_bases() {
|
|
let lib = include_str!("lib.rs");
|
|
let popover = include_str!("popover.rs");
|
|
assert!(lib.contains("pub mod popover;"));
|
|
assert!(lib.contains("popover::*"));
|
|
assert!(lib.contains("crate::popover::script_mod(vm);"));
|
|
let view_at = lib.find("crate::view::script_mod(vm);").unwrap();
|
|
let tip_at = lib.find("crate::tip::script_mod(vm);").unwrap();
|
|
let popover_at = lib.find("crate::popover::script_mod(vm);").unwrap();
|
|
assert!(view_at < popover_at);
|
|
assert!(tip_at < popover_at);
|
|
assert!(popover.contains("mod.widgets.PopoverBase = #(Popover::register_widget(vm))"));
|
|
assert!(popover.contains("mod.widgets.PopoverFlat = set_type_default() do mod.widgets.PopoverBase{"));
|
|
assert!(popover.contains("mod.widgets.Popover = mod.widgets.PopoverFlat{"));
|
|
assert_eq!(popover.matches("set_type_default() do mod.widgets.PopoverBase").count(), 1);
|
|
}
|
|
|
|
/// The presets build on the bevelled popover, and the confirm popover
|
|
/// has its own base and one default.
|
|
#[test]
|
|
fn test_popover_presets_and_confirm_are_registered() {
|
|
let popover = include_str!("popover.rs");
|
|
assert!(popover.contains("mod.widgets.PopoverArrow = mod.widgets.Popover{"));
|
|
assert!(popover.contains("mod.widgets.PopoverHover = mod.widgets.Popover{"));
|
|
assert!(popover.contains("mod.widgets.PopoverToggle = mod.widgets.Popover{"));
|
|
assert!(popover.contains("mod.widgets.ConfirmPopoverBase = #(ConfirmPopover::register_widget(vm))"));
|
|
assert!(popover.contains("mod.widgets.ConfirmPopover = set_type_default() do mod.widgets.ConfirmPopoverBase{"));
|
|
assert_eq!(popover.matches("set_type_default() do mod.widgets.ConfirmPopoverBase").count(), 1);
|
|
assert!(popover.contains("pub struct FocusTrap"));
|
|
}
|
|
}
|
|
|
|
/// The text of `widgets_mod`, where registration order is decided. The new
|
|
/// widgets' tests read order from here rather than from the whole file: the
|
|
/// tests themselves name every call, so a search of the whole file would
|
|
/// still find a call that had gone missing from the function.
|
|
#[cfg(test)]
|
|
fn widgets_mod_source() -> &'static str {
|
|
let lib = include_str!("lib.rs");
|
|
let start = lib.find("pub fn widgets_mod(vm: &mut ScriptVm)").expect("widgets_mod");
|
|
let end = lib.find("pub fn script_mod(vm: &mut ScriptVm)").expect("script_mod");
|
|
&lib[start..end]
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod image_slice_registration_tests {
|
|
/// The slicing arithmetic is a module of its own beside the image cache
|
|
/// whose fit it extends. It has no script module: its enums are exported
|
|
/// to the DSL by the image widget, next to `ImageFit`.
|
|
#[test]
|
|
fn test_image_slice_is_a_module_without_a_registration() {
|
|
let lib = include_str!("lib.rs");
|
|
assert!(lib.contains("\npub mod image_slice;"));
|
|
assert!(lib.contains("\n image_slice::*,"));
|
|
assert!(!crate::widgets_mod_source().contains("crate::image_slice::"));
|
|
}
|
|
}
|