Split inventory items and tackle views, standardize bait supply presentation, add status-effect icons, and align shop, logbook, art-kit, and shared utility layouts.
390 lines
14 KiB
GDScript
390 lines
14 KiB
GDScript
class_name UtilityPageStyle
|
|
extends RefCounted
|
|
|
|
const TuffyFont: Font = preload("res://ui/fonts/Tuffy_Bold.otf")
|
|
const PAPER: Color = Color("eee2bd")
|
|
const PAPER_ALT: Color = Color("f3ecd7")
|
|
const INK: Color = Color("28251f")
|
|
const MUTED_INK: Color = Color("5f5547")
|
|
const BORDER: Color = Color("473d2e")
|
|
# PAPER/INK remain available only for intentionally paper-like content such as
|
|
# a letter body. Technical surfaces use this shared ocean palette.
|
|
const OCEAN_PANEL_DEEP: Color = Color("0d2c3a")
|
|
const OCEAN_PANEL_MID: Color = Color("123f4e")
|
|
const OCEAN_FIELD: Color = Color("081f2b")
|
|
const OCEAN_BUTTON: Color = Color("123f4e")
|
|
const OCEAN_BUTTON_HOVER: Color = Color("196072")
|
|
const OCEAN_BUTTON_PRESSED: Color = Color("0b3341")
|
|
const OCEAN_SELECTED: Color = Color("238697")
|
|
const OCEAN_TEXT_PRIMARY: Color = Color("e6f7f7")
|
|
const OCEAN_TEXT_SECONDARY: Color = Color("9fcfd2")
|
|
const OCEAN_DISABLED: Color = Color("38535b")
|
|
const OCEAN_DANGER: Color = Color("9e4550")
|
|
const NAVY: Color = Color("092b3d")
|
|
const NAVY_HOVER: Color = Color("12465b")
|
|
const GREEN: Color = Color("31594d")
|
|
const LIGHT_TEXT: Color = Color("f5eed9")
|
|
const DISABLED_TEXT: Color = Color(0.33, 0.36, 0.35, 0.72)
|
|
const MOTION_TWEEN_META: StringName = &"utility_page_motion_tween"
|
|
const LAPTOP_RECT: Rect2 = Rect2(66.0, 132.0, 1148.0, 520.0)
|
|
const SUPPLY_BADGE_MIN_WIDTH: float = 46.0
|
|
const SUPPLY_BADGE_HEIGHT: float = 24.0
|
|
const SUPPLY_BADGE_EDGE_MARGIN: float = 3.0
|
|
const SUPPLY_BADGE_X_OFFSET: float = 18.0
|
|
const SUPPLY_BADGE_ROTATION_DEGREES: float = 15.0
|
|
const SUPPLY_BADGE_HORIZONTAL_PADDING: float = 10.0
|
|
const SUPPLY_BADGE_FONT_SIZE: int = 13
|
|
const SUPPLY_BADGE_COLOR: Color = Color("0b5558")
|
|
const SUPPLY_BADGE_TEXT_COLOR: Color = Color("e7f5f4")
|
|
|
|
|
|
static func apply_page(root: Control) -> void:
|
|
root.add_theme_font_override("font", TuffyFont)
|
|
|
|
|
|
static func supply_quantity_text(current: int, total: int) -> String:
|
|
return "%d/%d" % [maxi(current, 0), maxi(total, 0)]
|
|
|
|
|
|
static func add_supply_quantity_badge(
|
|
parent: Control,
|
|
current: int,
|
|
total: int,
|
|
badge_name: StringName = &"SupplyQuantityBadge",
|
|
top_margin: float = SUPPLY_BADGE_EDGE_MARGIN,
|
|
) -> Panel:
|
|
var badge := Panel.new()
|
|
badge.name = badge_name
|
|
badge.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
|
badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
badge.z_index = 3
|
|
parent.add_child(badge)
|
|
var quantity_label := Label.new()
|
|
quantity_label.name = "Quantity"
|
|
quantity_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
quantity_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
quantity_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
quantity_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
badge.add_child(quantity_label)
|
|
configure_supply_quantity_badge(
|
|
badge, quantity_label, current, total, top_margin
|
|
)
|
|
return badge
|
|
|
|
|
|
static func configure_supply_quantity_badge(
|
|
badge: Panel,
|
|
quantity_label: Label,
|
|
current: int,
|
|
total: int,
|
|
top_margin: float = SUPPLY_BADGE_EDGE_MARGIN,
|
|
) -> void:
|
|
var supply_text := supply_quantity_text(current, total)
|
|
var text_width: float = TuffyFont.get_string_size(
|
|
supply_text,
|
|
HORIZONTAL_ALIGNMENT_LEFT,
|
|
-1.0,
|
|
SUPPLY_BADGE_FONT_SIZE,
|
|
).x
|
|
var badge_width: float = maxf(
|
|
SUPPLY_BADGE_MIN_WIDTH,
|
|
ceilf(text_width + SUPPLY_BADGE_HORIZONTAL_PADDING),
|
|
)
|
|
badge.offset_left = (
|
|
-badge_width - SUPPLY_BADGE_EDGE_MARGIN + SUPPLY_BADGE_X_OFFSET
|
|
)
|
|
badge.offset_top = top_margin
|
|
badge.offset_right = -SUPPLY_BADGE_EDGE_MARGIN + SUPPLY_BADGE_X_OFFSET
|
|
badge.offset_bottom = top_margin + SUPPLY_BADGE_HEIGHT
|
|
badge.pivot_offset = Vector2(
|
|
badge_width * 0.5,
|
|
SUPPLY_BADGE_HEIGHT * 0.5,
|
|
)
|
|
badge.rotation_degrees = SUPPLY_BADGE_ROTATION_DEGREES
|
|
var badge_style := rounded_style(
|
|
SUPPLY_BADGE_COLOR,
|
|
roundi(SUPPLY_BADGE_HEIGHT * 0.5),
|
|
)
|
|
badge_style.anti_aliasing = false
|
|
badge.add_theme_stylebox_override("panel", badge_style)
|
|
quantity_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
quantity_label.text = supply_text
|
|
quantity_label.add_theme_font_override("font", TuffyFont)
|
|
quantity_label.add_theme_font_size_override(
|
|
"font_size", SUPPLY_BADGE_FONT_SIZE
|
|
)
|
|
quantity_label.add_theme_color_override(
|
|
"font_color", SUPPLY_BADGE_TEXT_COLOR
|
|
)
|
|
|
|
|
|
static func rounded_style(color: Color, radius: int) -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = color
|
|
style.set_border_width_all(0)
|
|
style.set_corner_radius_all(radius)
|
|
return style
|
|
|
|
|
|
static func build_laptop_screen(
|
|
root: Control,
|
|
laptop_rect: Rect2 = LAPTOP_RECT,
|
|
) -> MarginContainer:
|
|
var laptop := PanelContainer.new()
|
|
laptop.name = "UtilityMainBox"
|
|
laptop.position = laptop_rect.position
|
|
laptop.size = laptop_rect.size
|
|
laptop.add_theme_stylebox_override(
|
|
"panel", rounded_style(OCEAN_PANEL_MID, 24)
|
|
)
|
|
root.add_child(laptop)
|
|
|
|
# Page-specific minimum sizes must not resize the shared outer artwork.
|
|
# Keep the shell in a fixed Control so every utility page retains the same
|
|
# canonical rectangle even as its internal controls evolve.
|
|
var fixed_layout := Control.new()
|
|
fixed_layout.name = "UtilityMainBoxLayout"
|
|
fixed_layout.clip_contents = true
|
|
laptop.add_child(fixed_layout)
|
|
|
|
var shell_margin := MarginContainer.new()
|
|
shell_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
shell_margin.add_theme_constant_override("margin_left", 20)
|
|
shell_margin.add_theme_constant_override("margin_top", 18)
|
|
shell_margin.add_theme_constant_override("margin_right", 20)
|
|
shell_margin.add_theme_constant_override("margin_bottom", 24)
|
|
fixed_layout.add_child(shell_margin)
|
|
|
|
var screen := PanelContainer.new()
|
|
screen.add_theme_stylebox_override(
|
|
"panel", rounded_style(OCEAN_FIELD, 16)
|
|
)
|
|
shell_margin.add_child(screen)
|
|
|
|
var content_margin := MarginContainer.new()
|
|
content_margin.add_theme_constant_override("margin_left", 24)
|
|
content_margin.add_theme_constant_override("margin_top", 18)
|
|
content_margin.add_theme_constant_override("margin_right", 24)
|
|
content_margin.add_theme_constant_override("margin_bottom", 18)
|
|
screen.add_child(content_margin)
|
|
|
|
var base := ColorRect.new()
|
|
base.position = Vector2(
|
|
laptop_rect.position.x + 82.0,
|
|
laptop_rect.end.y - 6.0,
|
|
)
|
|
base.size = Vector2(laptop_rect.size.x - 164.0, 14.0)
|
|
base.color = OCEAN_PANEL_MID
|
|
base.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
root.add_child(base)
|
|
return content_margin
|
|
|
|
|
|
static func panel_style() -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = OCEAN_PANEL_DEEP
|
|
style.set_border_width_all(0)
|
|
style.set_corner_radius_all(16)
|
|
style.content_margin_left = 26
|
|
style.content_margin_right = 26
|
|
style.content_margin_top = 20
|
|
style.content_margin_bottom = 20
|
|
return style
|
|
|
|
|
|
static func row_style(selected: bool = false) -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = OCEAN_SELECTED if selected else OCEAN_PANEL_MID
|
|
style.set_border_width_all(0)
|
|
style.set_corner_radius_all(8)
|
|
style.content_margin_left = 12
|
|
style.content_margin_right = 12
|
|
style.content_margin_top = 8
|
|
style.content_margin_bottom = 8
|
|
return style
|
|
|
|
|
|
static func button_style(color: Color) -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = color
|
|
style.border_color = Color(0.76, 0.86, 0.78, 0.72)
|
|
style.set_border_width_all(2)
|
|
style.set_corner_radius_all(7)
|
|
style.content_margin_left = 14
|
|
style.content_margin_right = 14
|
|
style.content_margin_top = 8
|
|
style.content_margin_bottom = 8
|
|
return style
|
|
|
|
|
|
static func apply_button(button: BaseButton) -> void:
|
|
button.add_theme_font_override("font", TuffyFont)
|
|
button.add_theme_color_override("font_color", LIGHT_TEXT)
|
|
button.add_theme_color_override("font_hover_color", Color.WHITE)
|
|
button.add_theme_color_override("font_pressed_color", Color.WHITE)
|
|
button.add_theme_color_override("font_focus_color", Color.WHITE)
|
|
button.add_theme_color_override("font_disabled_color", DISABLED_TEXT)
|
|
button.add_theme_stylebox_override("normal", button_style(NAVY))
|
|
button.add_theme_stylebox_override("hover", button_style(NAVY_HOVER))
|
|
button.add_theme_stylebox_override("pressed", button_style(GREEN))
|
|
button.add_theme_stylebox_override("focus", button_style(NAVY_HOVER))
|
|
button.add_theme_stylebox_override(
|
|
"disabled", button_style(Color(0.37, 0.42, 0.40, 0.55))
|
|
)
|
|
button.custom_minimum_size.y = maxf(button.custom_minimum_size.y, 40.0)
|
|
|
|
|
|
static func ocean_button_style(color: Color) -> StyleBoxFlat:
|
|
var style := button_style(color)
|
|
style.set_border_width_all(0)
|
|
return style
|
|
|
|
|
|
static func apply_ocean_button(button: BaseButton) -> void:
|
|
button.add_theme_font_override("font", TuffyFont)
|
|
button.add_theme_color_override("font_color", OCEAN_TEXT_PRIMARY)
|
|
button.add_theme_color_override("font_hover_color", Color.WHITE)
|
|
button.add_theme_color_override("font_pressed_color", Color.WHITE)
|
|
button.add_theme_color_override("font_focus_color", Color.WHITE)
|
|
button.add_theme_color_override(
|
|
"font_disabled_color",
|
|
Color(OCEAN_TEXT_SECONDARY, 0.52),
|
|
)
|
|
button.add_theme_stylebox_override(
|
|
"normal", ocean_button_style(OCEAN_BUTTON)
|
|
)
|
|
button.add_theme_stylebox_override(
|
|
"hover", ocean_button_style(OCEAN_BUTTON_HOVER)
|
|
)
|
|
button.add_theme_stylebox_override(
|
|
"pressed", ocean_button_style(OCEAN_SELECTED)
|
|
)
|
|
button.add_theme_stylebox_override(
|
|
"focus", ocean_button_style(OCEAN_SELECTED)
|
|
)
|
|
button.add_theme_stylebox_override(
|
|
"disabled", ocean_button_style(OCEAN_DISABLED)
|
|
)
|
|
button.custom_minimum_size.y = maxf(button.custom_minimum_size.y, 40.0)
|
|
|
|
|
|
static func apply_compact_ocean_button(button: BaseButton) -> void:
|
|
apply_ocean_button(button)
|
|
for state: StringName in [
|
|
&"normal", &"hover", &"pressed", &"focus", &"disabled",
|
|
]:
|
|
var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat
|
|
if style == null:
|
|
continue
|
|
style.content_margin_left = 10.0
|
|
style.content_margin_right = 10.0
|
|
style.content_margin_top = 4.0
|
|
style.content_margin_bottom = 4.0
|
|
button.add_theme_stylebox_override(state, style)
|
|
button.custom_minimum_size.y = 34.0
|
|
|
|
|
|
static func apply_line_edit(edit: LineEdit) -> void:
|
|
edit.add_theme_font_override("font", TuffyFont)
|
|
edit.add_theme_color_override("font_color", LIGHT_TEXT)
|
|
edit.add_theme_color_override(
|
|
"font_placeholder_color", Color(0.82, 0.80, 0.71, 0.84)
|
|
)
|
|
edit.add_theme_color_override("font_selected_color", Color.WHITE)
|
|
edit.add_theme_color_override("caret_color", Color("fff0b6"))
|
|
edit.add_theme_color_override("selection_color", Color("2f7186"))
|
|
edit.add_theme_color_override("font_uneditable_color", Color("c9c2ae"))
|
|
edit.add_theme_stylebox_override("normal", button_style(NAVY))
|
|
edit.add_theme_stylebox_override("focus", button_style(NAVY_HOVER))
|
|
edit.add_theme_stylebox_override(
|
|
"read_only", button_style(Color(NAVY, 0.82))
|
|
)
|
|
|
|
|
|
static func apply_ocean_line_edit(edit: LineEdit) -> void:
|
|
edit.add_theme_font_override("font", TuffyFont)
|
|
edit.add_theme_color_override("font_color", OCEAN_TEXT_PRIMARY)
|
|
edit.add_theme_color_override(
|
|
"font_placeholder_color", Color(OCEAN_TEXT_SECONDARY, 0.78)
|
|
)
|
|
edit.add_theme_color_override("font_selected_color", Color.WHITE)
|
|
edit.add_theme_color_override("caret_color", OCEAN_TEXT_PRIMARY)
|
|
edit.add_theme_color_override("selection_color", OCEAN_SELECTED)
|
|
edit.add_theme_color_override("font_uneditable_color", OCEAN_TEXT_SECONDARY)
|
|
edit.add_theme_stylebox_override(
|
|
"normal", ocean_button_style(OCEAN_FIELD)
|
|
)
|
|
edit.add_theme_stylebox_override(
|
|
"focus", ocean_button_style(OCEAN_SELECTED)
|
|
)
|
|
edit.add_theme_stylebox_override(
|
|
"read_only", ocean_button_style(Color(OCEAN_FIELD, 0.82))
|
|
)
|
|
|
|
|
|
static func apply_text_edit(edit: TextEdit) -> void:
|
|
edit.add_theme_font_override("font", TuffyFont)
|
|
edit.add_theme_color_override("font_color", OCEAN_TEXT_PRIMARY)
|
|
edit.add_theme_color_override(
|
|
"font_placeholder_color", Color(OCEAN_TEXT_SECONDARY, 0.78)
|
|
)
|
|
edit.add_theme_color_override("font_selected_color", Color.WHITE)
|
|
edit.add_theme_color_override("caret_color", OCEAN_TEXT_PRIMARY)
|
|
edit.add_theme_color_override("selection_color", OCEAN_SELECTED)
|
|
edit.add_theme_stylebox_override("normal", ocean_button_style(OCEAN_FIELD))
|
|
edit.add_theme_stylebox_override("focus", ocean_button_style(OCEAN_SELECTED))
|
|
|
|
|
|
static func animate_in(control: Control) -> void:
|
|
_cancel_motion(control)
|
|
control.pivot_offset = control.size * 0.5
|
|
control.scale = Vector2.ONE * UIMotion.UTILITY_ENTER_SCALE
|
|
control.modulate.a = 0.0
|
|
control.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
var tween := control.create_tween()
|
|
control.set_meta(MOTION_TWEEN_META, tween)
|
|
tween.set_parallel(true)
|
|
tween.tween_property(
|
|
control, "scale", Vector2.ONE, UIMotion.UTILITY_ENTER_DURATION
|
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
|
tween.tween_property(
|
|
control, "modulate:a", 1.0, UIMotion.UTILITY_ENTER_DURATION
|
|
)
|
|
tween.finished.connect(func() -> void:
|
|
if is_instance_valid(control):
|
|
control.mouse_filter = Control.MOUSE_FILTER_PASS
|
|
control.remove_meta(MOTION_TWEEN_META)
|
|
)
|
|
|
|
|
|
static func animate_out(control: Control, completed: Callable) -> void:
|
|
_cancel_motion(control)
|
|
control.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
control.pivot_offset = control.size * 0.5
|
|
var tween := control.create_tween()
|
|
control.set_meta(MOTION_TWEEN_META, tween)
|
|
tween.set_parallel(true)
|
|
tween.tween_property(
|
|
control,
|
|
"scale",
|
|
Vector2.ONE * UIMotion.UTILITY_ENTER_SCALE,
|
|
UIMotion.UTILITY_EXIT_DURATION
|
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
|
tween.tween_property(
|
|
control, "modulate:a", 0.0, UIMotion.UTILITY_EXIT_DURATION
|
|
)
|
|
if completed.is_valid():
|
|
tween.finished.connect(completed, CONNECT_ONE_SHOT)
|
|
tween.finished.connect(func() -> void:
|
|
if is_instance_valid(control):
|
|
control.remove_meta(MOTION_TWEEN_META)
|
|
)
|
|
|
|
|
|
static func _cancel_motion(control: Control) -> void:
|
|
if not control.has_meta(MOTION_TWEEN_META):
|
|
return
|
|
var existing := control.get_meta(MOTION_TWEEN_META) as Tween
|
|
if existing != null and existing.is_valid():
|
|
existing.kill()
|
|
control.remove_meta(MOTION_TWEEN_META)
|