457 lines
15 KiB
Rust
457 lines
15 KiB
Rust
use crate::{
|
|
animator::{Animate, Animator, AnimatorAction, AnimatorImpl, Play},
|
|
makepad_derive_widget::*,
|
|
makepad_draw::*,
|
|
tab_close_button::{TabCloseButton, TabCloseButtonAction},
|
|
};
|
|
|
|
use crate::makepad_draw::DrawSvgGlyph;
|
|
|
|
script_mod! {
|
|
use mod.prelude.widgets_internal.*
|
|
use mod.widgets.*
|
|
|
|
mod.widgets.TabBase = #(Tab::script_component(vm))
|
|
|
|
mod.widgets.Tab = set_type_default() do mod.widgets.TabBase{
|
|
width: Fit
|
|
height: max(theme.tab_height, 23.)
|
|
|
|
align: Align{x: 0.0, y: 0.5}
|
|
padding: theme.mspace_3{top: theme.space_2 * 1.2}
|
|
margin: Inset{right: theme.space_1, top: theme.space_1}
|
|
|
|
close_button: TabCloseButton{}
|
|
|
|
draw_text +: {
|
|
hover: instance(0.0)
|
|
active: instance(0.0)
|
|
|
|
text_style: theme.font_regular{
|
|
font_size: theme.font_size_p
|
|
}
|
|
|
|
color_dither: uniform(1.0)
|
|
gradient_fill_horizontal: uniform(0.0)
|
|
|
|
color: theme.color_label_inner
|
|
color_hover: uniform(theme.color_label_inner_hover)
|
|
color_active: uniform(theme.color_label_inner_active)
|
|
|
|
color_2: uniform(vec4(-1.0, -1.0, -1.0, -1.0))
|
|
color_2_hover: uniform(#FA0)
|
|
color_2_active: uniform(#0F0)
|
|
|
|
get_color: fn() {
|
|
let mut col = self.color
|
|
let mut col_hover = self.color_hover
|
|
let mut col_active = self.color_active
|
|
|
|
if self.color_2.x > -0.5 {
|
|
let dither = Math.random_2d(self.pos.xy) * 0.04 * self.color_dither
|
|
let dir = if self.gradient_fill_horizontal > 0.5 self.pos.x + dither else self.pos.y + dither
|
|
col = mix(self.color, self.color_2, dir)
|
|
col_hover = mix(self.color_hover, self.color_2_hover, dir)
|
|
col_active = mix(self.color_active, self.color_2_active, dir)
|
|
}
|
|
|
|
return col
|
|
.mix(col_active, self.active)
|
|
.mix(col_hover, self.hover)
|
|
}
|
|
}
|
|
|
|
draw_bg +: {
|
|
hover: instance(0.0)
|
|
active: instance(0.0)
|
|
|
|
overlap_fix: uniform(1.0)
|
|
border_size: uniform(1.)
|
|
border_radius: uniform(theme.corner_radius)
|
|
gradient_border_horizontal: uniform(0.0)
|
|
gradient_fill_horizontal: uniform(0.0)
|
|
color_dither: uniform(1.)
|
|
|
|
color: uniform(theme.color_d_hidden)
|
|
color_hover: uniform(theme.color_u_hidden)
|
|
color_active: uniform(theme.color_bg_app)
|
|
|
|
color_2: uniform(vec4(-1.0, -1.0, -1.0, -1.0))
|
|
color_2_hover: uniform(theme.color_u_hidden)
|
|
color_2_active: uniform(theme.color_bg_app)
|
|
|
|
border_color: uniform(theme.color_u_hidden)
|
|
border_color_hover: uniform(theme.color_u_hidden)
|
|
border_color_active: uniform(theme.color_bevel_outset_1)
|
|
|
|
border_color_2: uniform(vec4(-1.0, -1.0, -1.0, -1.0))
|
|
border_color_2_hover: uniform(theme.color_d_hidden)
|
|
border_color_2_active: uniform(theme.color_d_hidden)
|
|
|
|
pixel: fn() {
|
|
let sdf = Sdf2d.viewport(self.pos * self.rect_size)
|
|
|
|
let border_sz_uv = vec2(
|
|
self.border_size / self.rect_size.x
|
|
self.border_size / self.rect_size.y
|
|
)
|
|
|
|
let sz_inner_px = vec2(
|
|
self.rect_size.x - self.border_size * 2.
|
|
self.rect_size.y - self.border_size * 2.
|
|
)
|
|
|
|
let scale_factor_fill = vec2(
|
|
self.rect_size.x / sz_inner_px.x
|
|
self.rect_size.y / sz_inner_px.y
|
|
)
|
|
|
|
sdf.box_y(
|
|
self.border_size + self.overlap_fix
|
|
self.border_size
|
|
self.rect_size.x - self.border_size * 2. - self.overlap_fix
|
|
self.rect_size.y
|
|
self.border_radius
|
|
max(self.border_size * 0.5, 0.5)
|
|
)
|
|
|
|
let mut color_fill = self.color
|
|
let mut color_fill_hover = self.color_hover
|
|
let mut color_fill_active = self.color_active
|
|
|
|
if self.color_2.x > -0.5 {
|
|
let dither = Math.random_2d(self.pos.xy) * 0.04 * self.color_dither
|
|
let gradient_fill = vec2(
|
|
self.pos.x * scale_factor_fill.x - border_sz_uv.x * 2. + dither
|
|
self.pos.y * scale_factor_fill.y - border_sz_uv.y * 2. + dither
|
|
)
|
|
let dir = if self.gradient_fill_horizontal > 0.5 gradient_fill.x else gradient_fill.y
|
|
color_fill = mix(self.color, self.color_2, dir)
|
|
color_fill_hover = mix(self.color_hover, self.color_2_hover, dir)
|
|
color_fill_active = mix(self.color_active, self.color_2_active, dir)
|
|
}
|
|
|
|
let mut color_stroke = self.border_color
|
|
let mut color_stroke_hover = self.border_color_hover
|
|
let mut color_stroke_active = self.border_color_active
|
|
|
|
if self.border_color_2.x > -0.5 {
|
|
let dither = Math.random_2d(self.pos.xy) * 0.04 * self.color_dither
|
|
let gradient_border = vec2(
|
|
self.pos.x + dither
|
|
self.pos.y + dither
|
|
)
|
|
let dir = if self.gradient_border_horizontal > 0.5 gradient_border.x else gradient_border.y
|
|
color_stroke = mix(self.border_color, self.border_color_2, dir)
|
|
color_stroke_hover = mix(self.border_color_hover, self.border_color_2_hover, dir)
|
|
color_stroke_active = mix(self.border_color_active, self.border_color_2_active, dir)
|
|
}
|
|
|
|
let fill = color_fill
|
|
.mix(color_fill_hover, self.hover)
|
|
.mix(color_fill_active, self.active)
|
|
|
|
let stroke = color_stroke
|
|
.mix(color_stroke_hover, self.hover)
|
|
.mix(color_stroke_active, self.active)
|
|
|
|
sdf.fill_keep(fill)
|
|
sdf.stroke(stroke, self.border_size)
|
|
|
|
return sdf.result
|
|
}
|
|
}
|
|
|
|
animator: Animator{
|
|
hover: {
|
|
default: @off
|
|
off: AnimatorState{
|
|
from: {all: Forward{duration: 0.2}}
|
|
apply: {
|
|
draw_bg: {hover: 0.0}
|
|
draw_text: {hover: 0.0}
|
|
}
|
|
}
|
|
|
|
on: AnimatorState{
|
|
cursor: MouseCursor.Hand
|
|
from: {all: Forward{duration: 0.1}}
|
|
apply: {
|
|
draw_bg: {hover: snap(1.0)}
|
|
draw_text: {hover: snap(1.0)}
|
|
}
|
|
}
|
|
}
|
|
|
|
active: {
|
|
default: @off
|
|
off: AnimatorState{
|
|
from: {all: Forward{duration: 0.3}}
|
|
apply: {
|
|
close_button: {draw_button: {active: 0.0}}
|
|
draw_bg: {active: 0.0}
|
|
draw_text: {active: 0.0}
|
|
}
|
|
}
|
|
|
|
on: AnimatorState{
|
|
from: {all: Snap}
|
|
apply: {
|
|
close_button: {draw_button: {active: 1.0}}
|
|
draw_bg: {active: 1.0}
|
|
draw_text: {active: 1.0}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mod.widgets.TabFlat = mod.widgets.Tab{
|
|
margin: 0.
|
|
padding: theme.mspace_3
|
|
|
|
draw_bg +: {
|
|
border_size: 1.
|
|
border_radius: 0.5
|
|
color_dither: 1.
|
|
|
|
color: theme.color_d_hidden
|
|
color_hover: theme.color_d_hidden
|
|
color_active: theme.color_fg_app
|
|
|
|
border_color: theme.color_u_hidden
|
|
border_color_hover: theme.color_u_hidden
|
|
border_color_active: theme.color_fg_app
|
|
|
|
border_color_2: theme.color_d_hidden
|
|
border_color_2_hover: theme.color_d_hidden
|
|
border_color_2_active: theme.color_fg_app
|
|
|
|
overlap_fix: 0.
|
|
}
|
|
}
|
|
|
|
mod.widgets.TabGradientX = mod.widgets.Tab{
|
|
draw_bg +: {
|
|
border_size: 1.
|
|
border_radius: theme.corner_radius
|
|
gradient_border_horizontal: 0.0
|
|
gradient_fill_horizontal: 1.0
|
|
color_dither: 1.
|
|
}
|
|
|
|
draw_text +: {
|
|
gradient_fill_horizontal: 1.0
|
|
}
|
|
}
|
|
|
|
mod.widgets.TabGradientY = mod.widgets.TabGradientX{
|
|
draw_bg +: {
|
|
border_size: theme.beveling
|
|
border_radius: theme.corner_radius
|
|
color_dither: 1.
|
|
}
|
|
|
|
draw_text +: {
|
|
gradient_fill_horizontal: 1.0
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Script, ScriptHook, Animator)]
|
|
pub struct Tab {
|
|
#[source]
|
|
source: ScriptObjectRef,
|
|
#[rust]
|
|
is_active: bool,
|
|
#[rust]
|
|
is_dragging: bool,
|
|
|
|
#[live]
|
|
draw_bg: DrawQuad,
|
|
#[live]
|
|
draw_icon: DrawSvgGlyph,
|
|
#[live]
|
|
draw_text: DrawText,
|
|
#[live]
|
|
icon_walk: Walk,
|
|
|
|
#[apply_default]
|
|
animator: Animator,
|
|
|
|
#[live]
|
|
close_button: TabCloseButton,
|
|
|
|
#[live]
|
|
closeable: bool,
|
|
#[live]
|
|
hover: f32,
|
|
#[live]
|
|
active: f32,
|
|
|
|
#[live(10.0)]
|
|
min_drag_dist: f64,
|
|
|
|
#[walk]
|
|
pub walk: Walk,
|
|
#[layout]
|
|
pub layout: Layout,
|
|
}
|
|
|
|
pub enum TabAction {
|
|
WasPressed,
|
|
CloseWasPressed,
|
|
ShouldTabStartDrag,
|
|
ShouldTabStopDrag,
|
|
/// A touch finger went down on this tab (for scroll tracking).
|
|
TouchDown {
|
|
abs: Vec2d,
|
|
time: f64,
|
|
},
|
|
/// A touch finger moved on this tab without a long press (scroll the tab bar).
|
|
TouchScroll {
|
|
abs: Vec2d,
|
|
time: f64,
|
|
},
|
|
/// A touch finger lifted off this tab without a long press (end scroll / flick).
|
|
TouchUp {
|
|
abs: Vec2d,
|
|
time: f64,
|
|
},
|
|
}
|
|
|
|
impl Tab {
|
|
pub fn is_active(&self) -> bool {
|
|
self.is_active
|
|
}
|
|
|
|
pub fn set_is_active(&mut self, cx: &mut Cx, is_active: bool, animate: Animate) {
|
|
self.is_active = is_active;
|
|
self.animator_toggle(cx, is_active, animate, ids!(active.on), ids!(active.off));
|
|
}
|
|
|
|
pub fn draw(&mut self, cx: &mut Cx2d, name: &str) {
|
|
self.draw_bg.begin(cx, self.walk, self.layout);
|
|
if self.closeable {
|
|
self.close_button.draw(cx);
|
|
}
|
|
|
|
self.draw_icon.draw_walk(cx, self.icon_walk);
|
|
self.draw_text
|
|
.draw_walk(cx, Walk::fit(), Align::default(), name);
|
|
self.draw_bg.end(cx);
|
|
}
|
|
|
|
pub fn area(&self) -> Area {
|
|
self.draw_bg.area()
|
|
}
|
|
|
|
/// Sets the draw depth on all draw primitives of this tab.
|
|
pub fn set_draw_depth(&mut self, depth: f32) {
|
|
self.draw_bg.draw_depth = depth;
|
|
self.draw_text.draw_depth = depth;
|
|
self.close_button.set_draw_depth(depth);
|
|
}
|
|
|
|
pub fn handle_event_with(
|
|
&mut self,
|
|
cx: &mut Cx,
|
|
event: &Event,
|
|
dispatch_action: &mut dyn FnMut(&mut Cx, TabAction),
|
|
) {
|
|
self.animator_handle_event(cx, event);
|
|
|
|
let mut block_hover_out = false;
|
|
match self.close_button.handle_event(cx, event) {
|
|
TabCloseButtonAction::WasPressed if self.closeable => {
|
|
dispatch_action(cx, TabAction::CloseWasPressed)
|
|
}
|
|
TabCloseButtonAction::HoverIn => block_hover_out = true,
|
|
TabCloseButtonAction::HoverOut => self.animator_play(cx, ids!(hover.off)),
|
|
_ => (),
|
|
};
|
|
|
|
match event.hits(cx, self.draw_bg.area()) {
|
|
Hit::FingerHoverIn(_) => {
|
|
self.animator_play(cx, ids!(hover.on));
|
|
}
|
|
Hit::FingerHoverOut(_) => {
|
|
if !block_hover_out {
|
|
self.animator_play(cx, ids!(hover.off));
|
|
}
|
|
}
|
|
Hit::FingerMove(e) => {
|
|
if e.device.is_touch() {
|
|
if e.has_long_press_occurred {
|
|
// Touch: drag the tab only after a long press.
|
|
if !self.is_dragging && (e.abs - e.abs_start).length() > self.min_drag_dist
|
|
{
|
|
self.is_dragging = true;
|
|
dispatch_action(cx, TabAction::ShouldTabStartDrag);
|
|
}
|
|
} else {
|
|
// Touch without long press: scroll the tab bar.
|
|
dispatch_action(
|
|
cx,
|
|
TabAction::TouchScroll {
|
|
abs: e.abs,
|
|
time: e.time,
|
|
},
|
|
);
|
|
}
|
|
} else {
|
|
// Mouse: drag the tab after exceeding the minimum drag distance.
|
|
if !self.is_dragging && (e.abs - e.abs_start).length() > self.min_drag_dist {
|
|
self.is_dragging = true;
|
|
dispatch_action(cx, TabAction::ShouldTabStartDrag);
|
|
}
|
|
}
|
|
}
|
|
Hit::FingerUp(fue) => {
|
|
if self.is_dragging {
|
|
dispatch_action(cx, TabAction::ShouldTabStopDrag);
|
|
self.is_dragging = false;
|
|
}
|
|
if fue.device.is_touch() {
|
|
if !fue.has_long_press_occurred {
|
|
dispatch_action(
|
|
cx,
|
|
TabAction::TouchUp {
|
|
abs: fue.abs,
|
|
time: fue.time,
|
|
},
|
|
);
|
|
}
|
|
// For touch: only select the tab if it was a simple tap,
|
|
// not a scroll gesture or long-press drag.
|
|
if fue.was_tap() {
|
|
dispatch_action(cx, TabAction::WasPressed);
|
|
}
|
|
}
|
|
}
|
|
Hit::FingerDown(fde) => {
|
|
if fde.device.is_touch() {
|
|
// For touch: don't select on finger down; selection happens
|
|
// on finger up if no gesture was recognized (see FingerUp above).
|
|
if fde.is_primary_hit() {
|
|
dispatch_action(
|
|
cx,
|
|
TabAction::TouchDown {
|
|
abs: fde.abs,
|
|
time: fde.time,
|
|
},
|
|
);
|
|
}
|
|
} else {
|
|
// Mouse: select immediately on click, middle-click to close.
|
|
if fde.is_primary_hit() {
|
|
dispatch_action(cx, TabAction::WasPressed);
|
|
} else if self.closeable && fde.mouse_button().is_some_and(|b| b.is_middle()) {
|
|
dispatch_action(cx, TabAction::CloseWasPressed);
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|