2026-07-31 10:20:18 -04:00
|
|
|
class_name OrganizerTab
|
|
|
|
|
extends Button
|
|
|
|
|
|
2026-07-31 13:14:55 -04:00
|
|
|
const PALETTE: Array[Color] = [
|
|
|
|
|
Color("b9deea"),
|
|
|
|
|
Color("3d9ca0"),
|
|
|
|
|
Color("4f9b72"),
|
|
|
|
|
]
|
|
|
|
|
const TEXT_COLOR := Color("102b35")
|
2026-07-31 10:20:18 -04:00
|
|
|
const RISE_DURATION: float = UIMotion.UTILITY_ENTER_DURATION
|
2026-07-31 13:14:55 -04:00
|
|
|
const INACTIVE_SETTLE_Y: float = 7.0
|
|
|
|
|
const SELECTED_SETTLE_Y: float = 1.0
|
|
|
|
|
const HOVER_SETTLE_Y: float = 5.0
|
2026-07-31 10:20:18 -04:00
|
|
|
const HIDDEN_SETTLE_Y: float = 30.0
|
2026-07-31 13:14:55 -04:00
|
|
|
const CAP_HEIGHT: float = 22.0
|
|
|
|
|
const FONT_SIZE: int = 15
|
|
|
|
|
|
|
|
|
|
@export_range(0, 2, 1) var palette_index: int = 0:
|
|
|
|
|
set(value):
|
|
|
|
|
palette_index = clampi(value, 0, PALETTE.size() - 1)
|
|
|
|
|
queue_redraw()
|
2026-07-31 10:20:18 -04:00
|
|
|
|
|
|
|
|
var _motion_tween: Tween
|
|
|
|
|
var _hovered: bool = false
|
|
|
|
|
var _motion_ready: bool = false
|
|
|
|
|
var _visual_y: float = INACTIVE_SETTLE_Y
|
2026-07-31 13:14:55 -04:00
|
|
|
var _selected: bool = false
|
2026-07-31 10:20:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
|
toggle_mode = true
|
2026-07-31 13:14:55 -04:00
|
|
|
_selected = button_pressed
|
2026-07-31 10:20:18 -04:00
|
|
|
add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
2026-07-31 13:14:55 -04:00
|
|
|
add_theme_font_size_override("font_size", FONT_SIZE)
|
2026-07-31 10:20:18 -04:00
|
|
|
for color_name: StringName in [
|
|
|
|
|
&"font_color",
|
|
|
|
|
&"font_hover_color",
|
|
|
|
|
&"font_focus_color",
|
|
|
|
|
&"font_pressed_color",
|
|
|
|
|
&"font_disabled_color",
|
|
|
|
|
]:
|
|
|
|
|
add_theme_color_override(color_name, Color.TRANSPARENT)
|
|
|
|
|
mouse_entered.connect(_set_hovered.bind(true))
|
|
|
|
|
mouse_exited.connect(_set_hovered.bind(false))
|
|
|
|
|
focus_entered.connect(refresh_state)
|
|
|
|
|
focus_exited.connect(refresh_state)
|
|
|
|
|
toggled.connect(_on_toggled)
|
|
|
|
|
_apply_empty_styles()
|
|
|
|
|
call_deferred("_initialize_motion")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func refresh_state(animate: bool = true) -> void:
|
|
|
|
|
queue_redraw()
|
|
|
|
|
if _motion_ready:
|
|
|
|
|
_move_to_target(animate)
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 13:14:55 -04:00
|
|
|
func set_selected(value: bool, animate: bool = true) -> void:
|
|
|
|
|
_selected = value
|
|
|
|
|
set_pressed_no_signal(value)
|
|
|
|
|
refresh_state(animate)
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 10:20:18 -04:00
|
|
|
func animate_entrance(delay: float = 0.0) -> void:
|
|
|
|
|
_cancel_motion()
|
|
|
|
|
_motion_ready = true
|
|
|
|
|
_set_visual_y(HIDDEN_SETTLE_Y)
|
|
|
|
|
_motion_tween = create_tween()
|
|
|
|
|
if delay > 0.0:
|
|
|
|
|
_motion_tween.tween_interval(delay)
|
|
|
|
|
_motion_tween.tween_method(
|
|
|
|
|
_set_visual_y, _visual_y, _target_y(), RISE_DURATION
|
|
|
|
|
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
|
|
|
|
|
_motion_tween.finished.connect(_clear_motion_tween, CONNECT_ONE_SHOT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func settle_for_close() -> void:
|
|
|
|
|
_cancel_motion()
|
|
|
|
|
if not _motion_ready:
|
|
|
|
|
return
|
|
|
|
|
_motion_tween = create_tween()
|
|
|
|
|
_motion_tween.tween_method(
|
|
|
|
|
_set_visual_y,
|
|
|
|
|
_visual_y,
|
|
|
|
|
HIDDEN_SETTLE_Y,
|
|
|
|
|
UIMotion.UTILITY_EXIT_DURATION,
|
|
|
|
|
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
|
|
|
|
|
_motion_tween.finished.connect(_clear_motion_tween, CONNECT_ONE_SHOT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _initialize_motion() -> void:
|
|
|
|
|
_motion_ready = true
|
|
|
|
|
_set_visual_y(_target_y())
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 13:14:55 -04:00
|
|
|
func _on_toggled(pressed: bool) -> void:
|
|
|
|
|
if _selected and not pressed:
|
|
|
|
|
# A selected organizer tab is a page marker, not a collapsible toggle.
|
|
|
|
|
# Restore without another signal before any lower-state frame is drawn.
|
|
|
|
|
set_pressed_no_signal(true)
|
|
|
|
|
refresh_state(false)
|
|
|
|
|
return
|
|
|
|
|
_selected = pressed
|
2026-07-31 10:20:18 -04:00
|
|
|
refresh_state()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_hovered(hovered: bool) -> void:
|
|
|
|
|
_hovered = hovered
|
|
|
|
|
refresh_state()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _target_y() -> float:
|
|
|
|
|
if button_pressed:
|
|
|
|
|
return SELECTED_SETTLE_Y
|
|
|
|
|
if _hovered or has_focus():
|
|
|
|
|
return HOVER_SETTLE_Y
|
|
|
|
|
return INACTIVE_SETTLE_Y
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _move_to_target(animate: bool) -> void:
|
|
|
|
|
_cancel_motion()
|
|
|
|
|
if not animate:
|
|
|
|
|
_set_visual_y(_target_y())
|
|
|
|
|
return
|
|
|
|
|
_motion_tween = create_tween()
|
|
|
|
|
_motion_tween.tween_method(
|
|
|
|
|
_set_visual_y, _visual_y, _target_y(), RISE_DURATION
|
|
|
|
|
).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
|
|
|
|
|
_motion_tween.finished.connect(_clear_motion_tween, CONNECT_ONE_SHOT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _draw() -> void:
|
2026-07-31 13:14:55 -04:00
|
|
|
var base_color: Color = PALETTE[palette_index]
|
|
|
|
|
base_color = (
|
|
|
|
|
base_color.lightened(0.06)
|
|
|
|
|
if button_pressed
|
|
|
|
|
else base_color.darkened(0.07)
|
|
|
|
|
)
|
2026-07-31 10:20:18 -04:00
|
|
|
if disabled:
|
2026-07-31 13:14:55 -04:00
|
|
|
base_color = base_color.lerp(Color("8a978f"), 0.62)
|
2026-07-31 10:20:18 -04:00
|
|
|
elif has_focus():
|
2026-07-31 13:14:55 -04:00
|
|
|
base_color = base_color.lightened(0.12)
|
2026-07-31 10:20:18 -04:00
|
|
|
elif _hovered:
|
2026-07-31 13:14:55 -04:00
|
|
|
base_color = base_color.lightened(0.08)
|
|
|
|
|
var width: float = size.x
|
|
|
|
|
var height: float = size.y
|
|
|
|
|
var points := PackedVector2Array([
|
|
|
|
|
Vector2(10.0, _visual_y),
|
|
|
|
|
Vector2(width - 10.0, _visual_y),
|
|
|
|
|
Vector2(width - 5.0, _visual_y + 5.0),
|
|
|
|
|
Vector2(width - 5.0, _visual_y + 17.0),
|
|
|
|
|
Vector2(width, _visual_y + 26.0),
|
|
|
|
|
Vector2(width, _visual_y + height),
|
|
|
|
|
Vector2(0.0, _visual_y + height),
|
|
|
|
|
Vector2(0.0, _visual_y + 26.0),
|
|
|
|
|
Vector2(5.0, _visual_y + 17.0),
|
|
|
|
|
Vector2(5.0, _visual_y + 5.0),
|
|
|
|
|
])
|
|
|
|
|
draw_colored_polygon(points, base_color)
|
2026-07-31 10:20:18 -04:00
|
|
|
var font: Font = UtilityPageStyle.TuffyFont
|
|
|
|
|
var ink: Color = Color(TEXT_COLOR, 0.52) if disabled else TEXT_COLOR
|
2026-07-31 13:14:55 -04:00
|
|
|
var baseline: float = _visual_y + CAP_HEIGHT * 0.5 + FONT_SIZE * 0.35
|
2026-07-31 10:20:18 -04:00
|
|
|
draw_string(
|
|
|
|
|
font,
|
2026-07-31 13:14:55 -04:00
|
|
|
Vector2(0.0, baseline),
|
2026-07-31 10:20:18 -04:00
|
|
|
text,
|
|
|
|
|
HORIZONTAL_ALIGNMENT_CENTER,
|
|
|
|
|
size.x,
|
2026-07-31 13:14:55 -04:00
|
|
|
FONT_SIZE,
|
2026-07-31 10:20:18 -04:00
|
|
|
ink,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _apply_empty_styles() -> void:
|
|
|
|
|
for style_name: StringName in [
|
|
|
|
|
&"normal",
|
|
|
|
|
&"hover",
|
|
|
|
|
&"focus",
|
|
|
|
|
&"pressed",
|
|
|
|
|
&"disabled",
|
|
|
|
|
]:
|
|
|
|
|
add_theme_stylebox_override(style_name, StyleBoxEmpty.new())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_visual_y(value: float) -> void:
|
|
|
|
|
_visual_y = value
|
|
|
|
|
queue_redraw()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _cancel_motion() -> void:
|
|
|
|
|
if _motion_tween != null:
|
|
|
|
|
_motion_tween.kill()
|
|
|
|
|
_motion_tween = null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _clear_motion_tween() -> void:
|
|
|
|
|
_motion_tween = null
|