* draw_text: adopt outer many_instances batch on linux/windows
CodeEditor opens an outer raster batch via DrawText::begin_many_instances
before its glyph loop. The linux/windows branch of DrawText::draw_text
ignored self.many_instances and opened its own nested batch, which
resolved (via find_appendable_drawcall) to the same draw_item whose
`instances` Vec was already swapped out by the outer open, panicking on
unwrap in Cx2d::begin_many_instances.
Mirror what the other platform branch (and draw_rasterized_glyphs_abs)
already do: take self.many_instances on entry, track whether the active
raster batch is the outer one, and hand it back on exit so the caller's
end_many_instances finalizes it. Skip the !drew_raster_this_frame area
clear when the outer batch is still live.
* Html/Markdown fixes: sub/superscript, table outlines/alignment, etc
- **Sub/sup in HTML**: added a `y_shift_scales` stack on `TextFlow`, composed onto `temp_y_shift` in `draw_text`. `<sub>` pushes `+0.55`, `<sup>` pushes `-0.2` in html.rs
- **Sub/sup in Markdown**: now handles `MdEvent::InlineHtml` for `<sub>`/`<sup>` (case-insensitive), using the same stacks.
- **Space after `&` (and other entities)**: the HTML lib's entity decoder now resets `last_non_whitespace` after truncate+push, so the whitespace-collapse check no longer drops the next real space.
- **Table column alignment (Markdown)**: `begin_table_cell` takes `align_x: f64`; tracks `Tag::Table` alignments and a per-row column index, passing each cell's `Alignment` through.
- **Table column alignment (HTML)**: `<td>`/`<th>` now honor `align="…"` and inline `style="text-align: …"` via new `cell_align_x` / `align_keyword_to_x` helpers in html.rs.
- **Per-row text alignment plumbing**: new `layout_align` field on `DrawText` is passed to the layouter, which already supports per-row alignment. `TextFlow` propagates a `cell_text_align_x` into it. This is the actual fix that makes cell alignment visible.
- **Wrap-flow alignment scaffolding**: implemented the previously-stubbed `Flow::Right { wrap: true }` branch in the turtle logic. Useful for non-text wrapping walks; text goes through the layouter path above.
- Add examples to uizoo: three new tables in both markdown and html tab -- a plain one, a left/center/right aligned one, and a numeric all-right-aligned one. They cover bold/italic/code/links/sub-sup/emoji/entities/strikethrough inside cells.
* minor cleanup; prefer `style` over `align` HTML tag
* Implement the `<details>`/`summary` widget within Html
* cleanup/improvement
* spacing and size consistency for details/summary header
295 lines
8.8 KiB
Rust
295 lines
8.8 KiB
Rust
use crate::{
|
|
animator::{Animate, Animator, AnimatorAction, AnimatorImpl, Play},
|
|
makepad_derive_widget::*,
|
|
makepad_draw::*,
|
|
widget::*,
|
|
};
|
|
|
|
script_mod! {
|
|
use mod.prelude.widgets_internal.*
|
|
use mod.widgets.*
|
|
|
|
mod.widgets.FoldButtonBase = #(FoldButton::register_widget(vm))
|
|
|
|
mod.widgets.FoldButton = set_type_default() do mod.widgets.FoldButtonBase{
|
|
height: 20
|
|
width: 15
|
|
margin: Inset{left: 0.}
|
|
|
|
draw_bg +: {
|
|
active: instance(1.0) // Default to open state (matches animator default: @on)
|
|
hover: instance(0.0)
|
|
|
|
color: uniform(theme.color_label_inner)
|
|
color_hover: uniform(theme.color_label_inner_hover)
|
|
color_active: uniform(theme.color_label_inner_active)
|
|
|
|
fade: uniform(1.0)
|
|
|
|
pixel: fn() {
|
|
let sz = 2.5
|
|
let c = vec2(5.0, self.rect_size.y * 0.4)
|
|
let sdf = Sdf2d.viewport(self.pos * self.rect_size)
|
|
sdf.clear(vec4(0.))
|
|
|
|
// we have 3 points, and need to rotate around its center
|
|
sdf.rotate(self.active * 0.5 * PI + 0.5 * PI, c.x, c.y)
|
|
sdf.move_to(c.x - sz, c.y + sz)
|
|
sdf.line_to(c.x, c.y - sz)
|
|
sdf.line_to(c.x + sz, c.y + sz)
|
|
sdf.close_path()
|
|
sdf.fill(
|
|
mix(
|
|
mix(self.color, self.color_hover, self.hover)
|
|
mix(self.color_active, self.color_hover, self.hover)
|
|
self.active
|
|
)
|
|
)
|
|
return sdf.result * self.fade
|
|
}
|
|
}
|
|
|
|
animator: Animator{
|
|
hover: {
|
|
default: @off
|
|
off: AnimatorState{
|
|
from: {all: Forward {duration: 0.1}}
|
|
apply: {draw_bg: {hover: 0.0}}
|
|
}
|
|
|
|
on: AnimatorState{
|
|
from: {all: Snap}
|
|
apply: {draw_bg: {hover: 1.0}}
|
|
}
|
|
}
|
|
|
|
active: {
|
|
default: @on
|
|
off: AnimatorState{
|
|
from: {all: Forward {duration: 0.2}}
|
|
ease: ExpDecay {d1: 0.96, d2: 0.97}
|
|
redraw: true
|
|
apply: {
|
|
active: 0.0
|
|
draw_bg: {active: 0.0}
|
|
}
|
|
}
|
|
on: AnimatorState{
|
|
from: {all: Forward {duration: 0.2}}
|
|
ease: ExpDecay {d1: 0.98, d2: 0.95}
|
|
redraw: true
|
|
apply: {
|
|
active: 1.0
|
|
draw_bg: {active: 1.0}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub enum FoldButtonAction {
|
|
#[default]
|
|
None,
|
|
Opening,
|
|
Closing,
|
|
Animating(f64),
|
|
}
|
|
|
|
#[derive(Script, ScriptHook, Widget, Animator)]
|
|
pub struct FoldButton {
|
|
#[uid]
|
|
uid: WidgetUid,
|
|
#[source]
|
|
source: ScriptObjectRef,
|
|
#[apply_default]
|
|
animator: Animator,
|
|
|
|
#[redraw]
|
|
#[live]
|
|
draw_bg: DrawQuad,
|
|
#[live]
|
|
abs_size: DVec2,
|
|
#[live]
|
|
abs_offset: DVec2,
|
|
#[walk]
|
|
walk: Walk,
|
|
#[live]
|
|
active: f64,
|
|
#[action_data]
|
|
#[rust]
|
|
action_data: WidgetActionData,
|
|
}
|
|
|
|
impl FoldButton {
|
|
pub fn set_is_open(&mut self, cx: &mut Cx, is_open: bool, animate: Animate) {
|
|
self.animator_toggle(cx, is_open, animate, ids!(active.on), ids!(active.off))
|
|
}
|
|
|
|
pub fn is_open(&self, cx: &Cx) -> bool {
|
|
self.animator_in_state(cx, ids!(active.on))
|
|
}
|
|
|
|
/// Overrides the triangle's base, hover, and active colors with a single
|
|
/// value. Intended for callers that want the triangle to match the color
|
|
/// of surrounding content (e.g. the HTML `<summary>` text color).
|
|
pub fn set_draw_color(&mut self, cx: &Cx, color: Vec4f) {
|
|
let values = [color.x, color.y, color.z, color.w];
|
|
self.draw_bg
|
|
.draw_vars
|
|
.set_uniform(cx, live_id!(color), &values);
|
|
self.draw_bg
|
|
.draw_vars
|
|
.set_uniform(cx, live_id!(color_hover), &values);
|
|
self.draw_bg
|
|
.draw_vars
|
|
.set_uniform(cx, live_id!(color_active), &values);
|
|
}
|
|
|
|
pub fn draw_walk_fold_button(&mut self, cx: &mut Cx2d, walk: Walk) {
|
|
self.draw_bg.draw_walk(cx, walk);
|
|
}
|
|
|
|
pub fn area(&self) -> Area {
|
|
self.draw_bg.area()
|
|
}
|
|
|
|
pub fn draw_abs(&mut self, cx: &mut Cx2d, pos: DVec2) {
|
|
self.draw_bg.draw_abs(
|
|
cx,
|
|
Rect {
|
|
pos: pos + self.abs_offset,
|
|
size: self.abs_size,
|
|
},
|
|
);
|
|
}
|
|
|
|
pub fn opening(&self, actions: &Actions) -> bool {
|
|
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
|
|
if let FoldButtonAction::Opening = item.cast() {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn closing(&self, actions: &Actions) -> bool {
|
|
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
|
|
if let FoldButtonAction::Closing = item.cast() {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn animating(&self, actions: &Actions) -> Option<f64> {
|
|
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
|
|
if let FoldButtonAction::Animating(v) = item.cast() {
|
|
return Some(v);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
impl Widget for FoldButton {
|
|
fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) {
|
|
let uid = self.widget_uid();
|
|
let res = self.animator_handle_event(cx, event);
|
|
if res.must_redraw() {
|
|
// Use self.active which is updated by the animator's script_apply
|
|
// (draw_bg.get_instance returns the OLD value from the previous draw)
|
|
cx.widget_action_with_data(
|
|
&self.action_data,
|
|
uid,
|
|
FoldButtonAction::Animating(self.active),
|
|
);
|
|
self.draw_bg.redraw(cx);
|
|
}
|
|
|
|
match event.hits(cx, self.draw_bg.area()) {
|
|
Hit::FingerDown(_fe) => {
|
|
if self.animator_in_state(cx, ids!(active.on)) {
|
|
self.animator_play(cx, ids!(active.off));
|
|
cx.widget_action_with_data(&self.action_data, uid, FoldButtonAction::Closing)
|
|
} else {
|
|
self.animator_play(cx, ids!(active.on));
|
|
cx.widget_action_with_data(&self.action_data, uid, FoldButtonAction::Opening)
|
|
}
|
|
self.animator_play(cx, ids!(hover.on));
|
|
}
|
|
Hit::FingerHoverIn(_) => {
|
|
cx.set_cursor(MouseCursor::Hand);
|
|
self.animator_play(cx, ids!(hover.on));
|
|
}
|
|
Hit::FingerHoverOut(_) => {
|
|
self.animator_play(cx, ids!(hover.off));
|
|
}
|
|
Hit::FingerUp(fe) => {
|
|
if fe.is_over {
|
|
if fe.device.has_hovers() {
|
|
self.animator_play(cx, ids!(hover.on));
|
|
} else {
|
|
self.animator_play(cx, ids!(hover.off));
|
|
}
|
|
} else {
|
|
self.animator_play(cx, ids!(hover.off));
|
|
}
|
|
}
|
|
_ => (),
|
|
};
|
|
}
|
|
|
|
fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep {
|
|
self.draw_walk_fold_button(cx, walk);
|
|
DrawStep::done()
|
|
}
|
|
}
|
|
|
|
impl FoldButtonRef {
|
|
pub fn opening(&self, actions: &Actions) -> bool {
|
|
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
|
|
if let FoldButtonAction::Opening = item.cast() {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn closing(&self, actions: &Actions) -> bool {
|
|
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
|
|
if let FoldButtonAction::Closing = item.cast() {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn animating(&self, actions: &Actions) -> Option<f64> {
|
|
if let Some(item) = actions.find_widget_action(self.widget_uid()) {
|
|
if let FoldButtonAction::Animating(v) = item.cast() {
|
|
return Some(v);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn open_float(&self) -> f64 {
|
|
if let Some(inner) = self.borrow() {
|
|
inner.active
|
|
} else {
|
|
1.0
|
|
}
|
|
}
|
|
|
|
pub fn set_is_open(&self, cx: &mut Cx, is_open: bool, animate: Animate) {
|
|
if let Some(mut inner) = self.borrow_mut() {
|
|
inner.set_is_open(cx, is_open, animate);
|
|
}
|
|
}
|
|
|
|
pub fn is_open(&self, cx: &Cx) -> bool {
|
|
self.borrow().map_or(true, |inner| inner.is_open(cx))
|
|
}
|
|
}
|