Improve art tools and UI docking

This commit is contained in:
Alexander Sellite 2026-08-02 09:20:44 -04:00
parent 10e0442437
commit 984f7c641c
13 changed files with 626 additions and 82 deletions

View file

@ -2,7 +2,7 @@ class_name ArtShopStock
extends RefCounted
const ART_KIT_ITEM_ID: StringName = &"art_kit"
const ART_KIT_PRICE: int = 1000
const ART_KIT_PRICE: int = 50
const UPGRADE_PRICE: int = 50
const MARKER_PRODUCTS: Array[StringName] = [

View file

@ -931,6 +931,10 @@ func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
settings.auto_click_enabled,
settings.auto_click_interval
)
_game_ui.set_edge_docks(
settings.chat_dock_right,
settings.paint_dock_right,
)
func _apply_world_pixelation(pixel_size: int) -> void:

View file

@ -28,6 +28,13 @@ const MAX_RECENT_REQUEST_IDS: int = 64
const GRID_PREVIEW_SURFACE_OFFSET: float = 0.03
const POINTER_EDGE_MARGIN: float = 2.0
enum GuideAction {
NONE,
HIDE,
RESTORE,
FINALIZE,
}
var _session: NetworkSession
var _spawn_service: PlayerSpawnService
var _relationships: PlayerRelationshipStore
@ -51,6 +58,10 @@ var _color_ids: Array[StringName] = []
var _color_index: int = 0
var _painting: bool = false
var _erasing: bool = false
var _eraser_mode: bool = false
var _armed_guide_action: int = GuideAction.NONE
var _armed_return_placement_mode: bool = false
var _armed_return_eraser_mode: bool = false
var _camera_look_active: bool = false
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
var _last_edited_cell := Vector3i(-1, -1, -1)
@ -127,6 +138,9 @@ func handle_input(event: InputEvent, can_open: bool) -> bool:
deactivate()
return false
if event.is_action_pressed("ui_cancel"):
if _armed_guide_action != GuideAction.NONE:
_cancel_armed_guide_action()
return true
if _placing_grid:
_set_placement_mode(false)
return true
@ -173,7 +187,9 @@ func handle_input(event: InputEvent, can_open: bool) -> bool:
MOUSE_BUTTON_LEFT:
if _placing_grid:
if mouse_event.pressed:
if mouse_event.shift_pressed and mouse_event.ctrl_pressed:
if _armed_guide_action != GuideAction.NONE:
_execute_armed_guide_action()
elif mouse_event.shift_pressed and mouse_event.ctrl_pressed:
_finalize_selected_guide()
elif mouse_event.shift_pressed:
_remove_selected_guide()
@ -182,8 +198,11 @@ func handle_input(event: InputEvent, can_open: bool) -> bool:
return true
if mouse_event.pressed:
_begin_stroke()
_painting = mouse_event.pressed and not mouse_event.shift_pressed
_erasing = mouse_event.pressed and mouse_event.shift_pressed
_erasing = (
mouse_event.pressed
and (mouse_event.shift_pressed or _eraser_mode)
)
_painting = mouse_event.pressed and not _erasing
if _painting or _erasing:
_submit_current_brush(_erasing, true)
else:
@ -210,6 +229,8 @@ func activate() -> void:
return
_active = true
_placing_grid = true
_eraser_mode = false
_clear_armed_guide_action(false)
_refresh_local_unlocks()
_rebuild_placement_preview()
_reset_stroke()
@ -228,6 +249,8 @@ func deactivate() -> void:
return
_active = false
_placing_grid = false
_eraser_mode = false
_clear_armed_guide_action(false)
_camera_look_active = false
_reset_stroke()
_selected_canvas_id = ""
@ -258,12 +281,63 @@ func set_color_id(color_id: StringName) -> bool:
var color_index: int = _color_ids.find(color_id)
if color_index < 0:
return false
_clear_armed_guide_action(true)
_eraser_mode = false
_color_index = color_index
_reset_stroke()
_emit_hud_state("")
return true
func set_eraser_mode(enabled: bool) -> void:
if not _active:
return
_clear_armed_guide_action(true)
_eraser_mode = enabled
if _eraser_mode and _placing_grid:
_placing_grid = false
_reset_stroke()
_selected_canvas_id = ""
_update_aim()
_refresh_stencil_visibility()
_emit_hud_state("eraser selected" if _eraser_mode else "marker selected")
func is_eraser_mode() -> bool:
return _eraser_mode
func arm_guide_action(action: int) -> bool:
if (
not _active
or action not in [GuideAction.HIDE, GuideAction.RESTORE, GuideAction.FINALIZE]
):
return false
if _armed_guide_action == action:
_cancel_armed_guide_action()
return false
if _armed_guide_action == GuideAction.NONE:
_armed_return_placement_mode = _placing_grid
_armed_return_eraser_mode = _eraser_mode
_armed_guide_action = action
_placing_grid = true
_eraser_mode = false
_reset_stroke()
_selected_canvas_id = ""
_update_aim()
_refresh_stencil_visibility()
_emit_hud_state("click a grid to %s it" % _guide_action_verb(action))
return true
func get_armed_guide_action() -> int:
return _armed_guide_action
func cancel_armed_guide_action() -> void:
_cancel_armed_guide_action()
func set_brush_size(value: int) -> bool:
if _art_unlocks == null or not _art_unlocks.is_brush_size_unlocked(value):
return false
@ -1751,7 +1825,11 @@ func _rebuild_placement_preview() -> void:
func _set_placement_mode(enabled: bool) -> void:
if not _active or _placing_grid == enabled:
if not _active:
return
_clear_armed_guide_action(true)
if _placing_grid == enabled:
_emit_hud_state("")
return
_placing_grid = enabled
_reset_stroke()
@ -1767,6 +1845,51 @@ func _set_placement_mode(enabled: bool) -> void:
)
func _execute_armed_guide_action() -> void:
var action: int = _armed_guide_action
match action:
GuideAction.HIDE:
_remove_selected_guide()
GuideAction.RESTORE:
_restore_selected_guide()
GuideAction.FINALIZE:
_finalize_selected_guide()
_clear_armed_guide_action(true)
_emit_hud_state("")
func _cancel_armed_guide_action() -> void:
if _armed_guide_action == GuideAction.NONE:
return
_clear_armed_guide_action(true)
_emit_hud_state("")
func _clear_armed_guide_action(restore_tool: bool) -> void:
if _armed_guide_action == GuideAction.NONE:
return
_armed_guide_action = GuideAction.NONE
if restore_tool:
_placing_grid = _armed_return_placement_mode
_eraser_mode = _armed_return_eraser_mode
_armed_return_placement_mode = false
_armed_return_eraser_mode = false
_reset_stroke()
_update_aim()
_refresh_stencil_visibility()
func _guide_action_verb(action: int) -> String:
match action:
GuideAction.HIDE:
return "hide"
GuideAction.RESTORE:
return "restore"
GuideAction.FINALIZE:
return "finish"
return "use"
func _remove_selected_guide() -> void:
if _selected_canvas_id.is_empty():
_emit_hud_state("aim at a placed grid before removing its guide")
@ -1782,6 +1905,21 @@ func _remove_selected_guide() -> void:
_emit_hud_state("hiding grid guide...")
func _restore_selected_guide() -> void:
if _selected_canvas_id.is_empty():
_emit_hud_state("aim at a hidden grid before restoring its guide")
return
var state: Dictionary = _canvas_states.get(_selected_canvas_id, {})
if state.is_empty() or bool(state.get("finalized", false)):
return
if bool(state.get("guide_visible", true)):
_emit_hud_state("this grid guide is already visible")
return
if request_guide_visibility(_selected_canvas_id, true):
if not _session.is_host():
_emit_hud_state("restoring grid guide...")
func _finalize_selected_guide() -> void:
if _selected_canvas_id.is_empty():
_emit_hud_state("aim at an active grid before finishing it")
@ -1805,6 +1943,8 @@ func _clamped_pointer_position(value: Vector2) -> Vector2:
func _cycle_color(direction: int) -> void:
if _color_ids.is_empty():
return
_clear_armed_guide_action(true)
_eraser_mode = false
_color_index = posmod(_color_index + direction, _color_ids.size())
_reset_stroke()
_emit_hud_state("")

View file

@ -29,6 +29,8 @@ const UI_COMPACT_RENDER_HEIGHTS: Array[int] = [0, 408, 336, 264, 192]
@export var invert_camera_y: bool = false
@export var chat_draft: String = ""
@export var chat_collapsed: bool = false
@export var chat_dock_right: bool = false
@export var paint_dock_right: bool = true
@export_range(1, 5, 1) var world_pixel_size: int = DEFAULT_WORLD_PIXEL_SIZE
@export_range(1, 5, 1) var ui_pixel_size: int = DEFAULT_UI_PIXEL_SIZE
@ -62,6 +64,8 @@ func copy() -> PlayerSettings:
result.invert_camera_y = invert_camera_y
result.chat_draft = chat_draft
result.chat_collapsed = chat_collapsed
result.chat_dock_right = chat_dock_right
result.paint_dock_right = paint_dock_right
result.world_pixel_size = world_pixel_size
result.ui_pixel_size = ui_pixel_size
return result

View file

@ -57,6 +57,14 @@ func load_settings() -> bool:
presentation.has("chat_collapsed")
and typeof(presentation["chat_collapsed"]) != TYPE_BOOL
)
or (
presentation.has("chat_dock_right")
and typeof(presentation["chat_dock_right"]) != TYPE_BOOL
)
or (
presentation.has("paint_dock_right")
and typeof(presentation["paint_dock_right"]) != TYPE_BOOL
)
):
return _use_defaults_after_corruption("Player settings values are invalid.")
var loaded := PlayerSettings.new()
@ -98,6 +106,8 @@ func load_settings() -> bool:
PlayerSettings.MAX_CHAT_DRAFT_CHARACTERS
)
loaded.chat_collapsed = bool(presentation.get("chat_collapsed", false))
loaded.chat_dock_right = bool(presentation.get("chat_dock_right", false))
loaded.paint_dock_right = bool(presentation.get("paint_dock_right", true))
if not loaded.is_valid():
return _use_defaults_after_corruption("Player settings values are invalid.")
current_settings = loaded
@ -170,6 +180,8 @@ func save_now() -> bool:
"ui_pixel_size": current_settings.ui_pixel_size,
"chat_draft": current_settings.chat_draft,
"chat_collapsed": current_settings.chat_collapsed,
"chat_dock_right": current_settings.chat_dock_right,
"paint_dock_right": current_settings.paint_dock_right,
},
}
if current_settings.chat_draft.is_empty():

View file

@ -34,7 +34,50 @@ func _run() -> void:
var toolbar := game_ui.get_node(
"%SurfaceDrawingToolbar"
) as SurfaceDrawingToolbar
var chat_ui := game_ui.get_node("%ChatUI") as ChatUI
assert(player != null and service != null and toolbar != null)
assert(chat_ui != null)
var settings_panel := game_ui.get(
"_pause_settings_panel"
) as SettingsPanel
var chat_dock_button := settings_panel.get_node("%ChatDock") as BubbleButton
var paint_dock_button := settings_panel.get_node("%PaintDock") as BubbleButton
assert(chat_dock_button.neutral_size.x == chat_dock_button.neutral_size.y)
assert(paint_dock_button.neutral_size.x == paint_dock_button.neutral_size.y)
assert(
chat_dock_button.compact_minimum_size.x
== chat_dock_button.compact_minimum_size.y
)
assert(
paint_dock_button.compact_minimum_size.x
== paint_dock_button.compact_minimum_size.y
)
assert(not chat_ui.is_docked_right())
assert(toolbar.is_docked_right())
var settings_manager := main.get_node(
"%PlayerSettingsManager"
) as PlayerSettingsManager
assert(settings_manager != null)
var changed_docks: PlayerSettings = settings_manager.current_settings.copy()
changed_docks.chat_dock_right = true
changed_docks.paint_dock_right = false
assert(settings_manager.apply_settings(changed_docks))
await process_frame
assert(chat_ui.is_docked_right())
assert(not toolbar.is_docked_right())
var reloaded_settings := PlayerSettingsManager.new()
root.add_child(reloaded_settings)
assert(reloaded_settings.load_settings())
assert(reloaded_settings.current_settings.chat_dock_right)
assert(not reloaded_settings.current_settings.paint_dock_right)
reloaded_settings.queue_free()
var default_docks: PlayerSettings = settings_manager.current_settings.copy()
default_docks.chat_dock_right = false
default_docks.paint_dock_right = true
assert(settings_manager.apply_settings(default_docks))
await process_frame
assert(not chat_ui.is_docked_right())
assert(toolbar.is_docked_right())
assert(not service.can_activate())
assert(not service.is_active() and not toolbar.visible)
assert(player.bag.add_item(ArtShopStock.ART_KIT_ITEM_ID, 1))
@ -47,12 +90,53 @@ func _run() -> void:
await process_frame
assert(service.is_active() and service.is_placement_mode())
assert(toolbar.visible)
assert(toolbar.position.is_equal_approx(Vector2(18.0, 18.0)))
var ui_root := game_ui.get_node("%UIRoot") as Control
assert(toolbar.get_parent() == ui_root)
assert(is_zero_approx(toolbar.position.y))
assert(
is_equal_approx(
toolbar.get_global_rect().end.x,
ui_root.get_global_rect().end.x,
)
)
assert(toolbar.get_global_rect().end.x <= 1280.0)
assert(toolbar.get_global_rect().end.y <= 720.0)
var top_panel := toolbar.get_node("%TopPanel") as PanelContainer
var color_panel := toolbar.get_node("%ColorPanel") as PanelContainer
assert(top_panel.position.is_equal_approx(Vector2.ZERO))
assert(color_panel.position.x > 0.0)
assert(is_equal_approx(color_panel.get_rect().end.x, top_panel.get_rect().end.x))
assert(color_panel.position.y < top_panel.get_rect().end.y)
assert(color_panel.get_rect().end.y > top_panel.get_rect().end.y)
var toolbar_origin: Vector2 = toolbar.get_global_rect().position
var top_pointer := InputEventMouseMotion.new()
top_pointer.position = toolbar_origin + Vector2(200.0, 24.0)
assert(toolbar.owns_pointer_event(top_pointer))
var rail_pointer := InputEventMouseMotion.new()
rail_pointer.position = color_panel.get_global_rect().get_center()
assert(toolbar.owns_pointer_event(rail_pointer))
var empty_pointer := InputEventMouseMotion.new()
empty_pointer.position = toolbar_origin + Vector2(200.0, 180.0)
assert(not toolbar.owns_pointer_event(empty_pointer))
var brush_option := toolbar.get_node("%BrushOption") as OptionButton
var grid_option := toolbar.get_node("%GridOption") as OptionButton
var mode_button := toolbar.get_node("%ModeButton") as Button
var eraser_button := toolbar.get_node("%EraserButton") as Button
var undo_button := toolbar.get_node("%UndoButton") as Button
var hide_button := toolbar.get_node("%HideGuideButton") as Button
var restore_button := toolbar.get_node("%RestoreGuideButton") as Button
var finalize_button := toolbar.get_node("%FinalizeGuideButton") as Button
var close_button := toolbar.get_node("%CloseButton") as Button
assert(
mode_button != null
and eraser_button != null
and undo_button != null
and hide_button != null
and restore_button != null
and finalize_button != null
and close_button != null
)
assert(brush_option.item_count == 4)
assert(grid_option.item_count == 4)
assert(not brush_option.get_popup().is_item_disabled(0))
@ -79,6 +163,58 @@ func _run() -> void:
assert(service.get_brush_size() == 4)
assert(service.get_grid_size() == 128)
mode_button.pressed.emit()
assert(not service.is_placement_mode())
eraser_button.pressed.emit()
assert(service.is_eraser_mode())
assert(eraser_button.button_pressed)
hide_button.pressed.emit()
assert(service.is_placement_mode())
assert(not service.is_eraser_mode())
assert(
service.get_armed_guide_action()
== NetworkSurfaceDrawingService.GuideAction.HIDE
)
var world_click := InputEventMouseButton.new()
world_click.button_index = MOUSE_BUTTON_LEFT
world_click.pressed = true
assert(service.handle_input(world_click, true))
assert(
service.get_armed_guide_action()
== NetworkSurfaceDrawingService.GuideAction.NONE
)
assert(not service.is_placement_mode())
assert(service.is_eraser_mode())
restore_button.pressed.emit()
assert(
service.get_armed_guide_action()
== NetworkSurfaceDrawingService.GuideAction.RESTORE
)
restore_button.pressed.emit()
assert(
service.get_armed_guide_action()
== NetworkSurfaceDrawingService.GuideAction.NONE
)
assert(not service.is_placement_mode() and service.is_eraser_mode())
finalize_button.pressed.emit()
assert(
service.get_armed_guide_action()
== NetworkSurfaceDrawingService.GuideAction.FINALIZE
)
assert(service.handle_input(world_click, true))
assert(not service.is_placement_mode() and service.is_eraser_mode())
(color_buttons[&"ocean_teal"] as Button).pressed.emit()
assert(not service.is_eraser_mode())
undo_button.pressed.emit()
assert(service.is_active())
close_button.pressed.emit()
assert(not service.is_active() and not toolbar.visible)
assert(service.handle_input(paint_key, true))
await process_frame
assert(service.is_active() and toolbar.visible)
assert(service.handle_input(paint_key, true))
await process_frame
assert(not service.is_active() and not toolbar.visible)

View file

@ -61,6 +61,7 @@ var _pending_send_body: String = ""
var _prior_movement: bool = true
var _prior_camera: bool = true
var _output_scale: float = 1.0
var _dock_right: bool = false
func _ready() -> void:
@ -85,6 +86,7 @@ func setup(
_player = player
_fishing_spot = fishing_spot
_settings = settings
set_dock_right(_settings.current_settings.chat_dock_right)
_service.message_received.connect(_on_message)
_service.local_message_confirmed.connect(_on_local_message_confirmed)
_service.history_replaced.connect(_on_history)
@ -333,10 +335,10 @@ func _build_ui() -> void:
func _chat_panel_style() -> StyleBoxFlat:
var style := _borderless_style(Color(0.025, 0.13, 0.19, 0.94))
style.corner_radius_top_left = 0
style.corner_radius_bottom_left = 0
style.corner_radius_top_right = 10
style.corner_radius_bottom_right = 10
style.corner_radius_top_left = 10 if _dock_right else 0
style.corner_radius_bottom_left = 10 if _dock_right else 0
style.corner_radius_top_right = 0 if _dock_right else 10
style.corner_radius_bottom_right = 0 if _dock_right else 10
style.content_margin_left = 12
style.content_margin_right = 12
style.content_margin_top = 10
@ -568,10 +570,26 @@ func set_output_scale(output_scale: float) -> void:
_output_scale = maxf(output_scale, 0.001)
func set_dock_right(should_dock_right: bool) -> void:
_dock_right = should_dock_right
if not is_node_ready() or _panel == null:
return
_panel.add_theme_stylebox_override("panel", _chat_panel_style())
_refresh_handle_labels(_presentation_state)
_layout_presentation(false)
func is_docked_right() -> bool:
return _dock_right
func _refresh_handle_labels(state: PresentationState) -> void:
var collapsed := state == PresentationState.COLLAPSED
var height_state := _visible_state_before_collapse if collapsed else state
_collapse_button.text = ">" if collapsed else "<"
if _dock_right:
_collapse_button.text = "<" if collapsed else ">"
else:
_collapse_button.text = ">" if collapsed else "<"
_collapse_button.tooltip_text = (
"Show chat" if collapsed else "Hide chat"
)
@ -687,6 +705,9 @@ func _set_status(text: String) -> void:
func _layout_presentation(animate: bool) -> void:
if _panel == null:
return
var viewport_width := size.x
if viewport_width <= 0.0:
viewport_width = 1280.0
var viewport_height := size.y
if viewport_height <= 0.0:
viewport_height = 720.0
@ -704,32 +725,40 @@ func _layout_presentation(animate: bool) -> void:
if layout_state == PresentationState.EXPANDED:
height = maxf(MIN_HISTORY_HEIGHT, viewport_height - 2.0 * bottom_margin)
height = minf(height, maxf(MIN_HISTORY_HEIGHT, bottom - MIN_TOP_MARGIN))
var target_position := Vector2(
-(PANEL_WIDTH - COLLAPSED_REVEAL_WIDTH)
if _presentation_state == PresentationState.COLLAPSED
else 0.0,
bottom - height,
)
var collapsed := _presentation_state == PresentationState.COLLAPSED
var panel_x: float
if _dock_right:
panel_x = (
viewport_width - COLLAPSED_REVEAL_WIDTH
if collapsed
else viewport_width - PANEL_WIDTH
)
else:
panel_x = -(PANEL_WIDTH - COLLAPSED_REVEAL_WIDTH) if collapsed else 0.0
var target_position := Vector2(panel_x, bottom - height)
var target_size := Vector2(PANEL_WIDTH, height)
var handle_stack_height := HANDLE_SIZE.y * 2.0 + HANDLE_GAP
var handle_stack_top := (
bottom - COMPACT_HEIGHT * 0.5 - handle_stack_height * 0.5
)
var handle_x: float
if _dock_right:
handle_x = panel_x - HANDLE_SIZE.x
else:
handle_x = COLLAPSED_REVEAL_WIDTH if collapsed else PANEL_WIDTH
var collapse_position := Vector2(
COLLAPSED_REVEAL_WIDTH
if _presentation_state == PresentationState.COLLAPSED
else PANEL_WIDTH,
handle_x,
handle_stack_top + HANDLE_SIZE.y + HANDLE_GAP,
)
var height_position := Vector2(
COLLAPSED_REVEAL_WIDTH
if _presentation_state == PresentationState.COLLAPSED
else PANEL_WIDTH,
handle_stack_top,
)
var unread_position := collapse_position + Vector2(6.0, -18.0)
var height_position := Vector2(handle_x, handle_stack_top)
var unread_offset_x := -18.0 if _dock_right else 6.0
var unread_position := collapse_position + Vector2(unread_offset_x, -18.0)
var hint_position := Vector2(
HINT_EDGE_MARGIN,
(
viewport_width - _hint.size.x - HINT_EDGE_MARGIN
if _dock_right
else HINT_EDGE_MARGIN
),
viewport_height - _hint.size.y - HINT_EDGE_MARGIN,
)
if _height_tween != null and _height_tween.is_valid():

View file

@ -219,6 +219,10 @@ func setup(
_shop_interaction = shop_interaction
_surface_drawing = surface_drawing
_surface_drawing_toolbar.setup(_surface_drawing, art_unlocks)
set_edge_docks(
settings_manager.current_settings.chat_dock_right,
settings_manager.current_settings.paint_dock_right,
)
func _input(event: InputEvent) -> void:
@ -409,6 +413,11 @@ func set_effective_ui_pixel_size(pixel_size: int) -> void:
_pause_settings_panel.set_effective_ui_pixel_size(pixel_size)
func set_edge_docks(chat_dock_right: bool, paint_dock_right: bool) -> void:
_chat_ui.set_dock_right(chat_dock_right)
_surface_drawing_toolbar.set_dock_right(paint_dock_right)
func focus_open_settings_back_button() -> void:
if _title_settings_panel.visible:
_title_settings_panel.focus_back_button()

View file

@ -46,7 +46,7 @@ mouse_filter = 2
unique_name_in_owner = true
mouse_filter = 2
[node name="SurfaceDrawingToolbar" parent="UIRoot/CanonicalStage" instance=ExtResource("11_drawing_toolbar")]
[node name="SurfaceDrawingToolbar" parent="UIRoot" instance=ExtResource("11_drawing_toolbar")]
unique_name_in_owner = true
[node name="GameplayTransientHUD" type="Control" parent="UIRoot/CanonicalStage"]

View file

@ -41,6 +41,8 @@ enum PresentationMode {
@onready var _world_value: BubbleButton = %WorldValue
@onready var _ui_value: BubbleButton = %UIValue
@onready var _chat_dock: BubbleButton = %ChatDock
@onready var _paint_dock: BubbleButton = %PaintDock
@onready var _mouse_value: BubbleButton = %MouseValue
@onready var _controller_value: BubbleButton = %ControllerValue
@onready var _invert_y_toggle: BubbleButton = %InvertYToggle
@ -133,6 +135,8 @@ func _ready() -> void:
_ui_options[index].pressed.connect(
_set_ui_pixelation.bind(index + 1)
)
_chat_dock.pressed.connect(_toggle_chat_dock)
_paint_dock.pressed.connect(_toggle_paint_dock)
%MouseDecrease.pressed.connect(_adjust_mouse_sensitivity.bind(-1))
%MouseIncrease.pressed.connect(_adjust_mouse_sensitivity.bind(1))
_mouse_value.pressed.connect(_adjust_mouse_sensitivity.bind(1))
@ -642,14 +646,12 @@ func _apply_settings() -> void:
if _settings_manager == null:
_feedback.text = "settings are unavailable."
return
var edited := PlayerSettings.new()
var edited: PlayerSettings = _settings_manager.current_settings.copy()
edited.auto_click_enabled = _auto_click_enabled
edited.auto_click_interval = _auto_click_interval_value
edited.mouse_camera_sensitivity = _mouse_sensitivity
edited.controller_camera_sensitivity = _controller_sensitivity
edited.invert_camera_y = _invert_camera_y
edited.world_pixel_size = _settings_manager.current_settings.world_pixel_size
edited.ui_pixel_size = _settings_manager.current_settings.ui_pixel_size
if _settings_manager.apply_settings(edited):
if (
_presentation_mode == PresentationMode.TITLE_EMBEDDED
@ -686,6 +688,10 @@ func _refresh_value_labels() -> void:
"ui\npixelation\n"
+ _pixel_size_label(settings.ui_pixel_size)
)
_chat_dock.text = "chat dock\n" + ("right" if settings.chat_dock_right else "left")
_paint_dock.text = (
"paint dock\n" + ("right" if settings.paint_dock_right else "left")
)
_mouse_value.text = "mouse\nsensitivity\n%.4f" % _mouse_sensitivity
_controller_value.text = (
"controller\nsensitivity\n%.1f" % _controller_sensitivity
@ -757,6 +763,28 @@ func _set_ui_pixelation(pixel_size: int) -> void:
_refresh_value_labels()
func _toggle_chat_dock() -> void:
if _settings_manager == null:
return
var edited: PlayerSettings = _settings_manager.current_settings.copy()
edited.chat_dock_right = not edited.chat_dock_right
if not _settings_manager.apply_settings(edited):
_feedback.text = "failed to save chat dock setting."
return
_refresh_value_labels()
func _toggle_paint_dock() -> void:
if _settings_manager == null:
return
var edited: PlayerSettings = _settings_manager.current_settings.copy()
edited.paint_dock_right = not edited.paint_dock_right
if not _settings_manager.apply_settings(edited):
_feedback.text = "failed to save paint dock setting."
return
_refresh_value_labels()
func _adjust_mouse_sensitivity(direction: int) -> void:
_mouse_sensitivity = clampf(
_mouse_sensitivity + float(direction) * 0.0005,

View file

@ -157,8 +157,8 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_page")
page_id = &"display"
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/WorldValue"), NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UIValue"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/DisplayBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/DisplayBackButton")])
bubble_paths = Array[NodePath]([NodePath("BubbleCluster/WorldValue"), NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UIValue"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/DisplayBackButton")])
focus_paths = Array[NodePath]([NodePath("BubbleCluster/WorldLegible"), NodePath("BubbleCluster/WorldCute"), NodePath("BubbleCluster/WorldRetro"), NodePath("BubbleCluster/WorldHardcore"), NodePath("BubbleCluster/WorldWtf"), NodePath("BubbleCluster/UILegible"), NodePath("BubbleCluster/UICute"), NodePath("BubbleCluster/UIRetro"), NodePath("BubbleCluster/UIHardcore"), NodePath("BubbleCluster/UIWtf"), NodePath("BubbleCluster/ChatDock"), NodePath("BubbleCluster/PaintDock"), NodePath("BubbleCluster/DisplayBackButton")])
initial_focus_path = NodePath("BubbleCluster/WorldRetro")
back_focus_path = NodePath("BubbleCluster/DisplayBackButton")
compact_maximum_layout_size = Vector2(544, 400)
@ -178,12 +178,12 @@ custom_minimum_size = Vector2(0, 0)
mouse_filter = 2
focus_mode = 0
text = "world\npixelation\nretro"
neutral_size = Vector2(160, 150)
neutral_size = Vector2(140, 130)
desktop_anchor = Vector2(160, 240)
compact_anchor = Vector2(175, 210)
compact_minimum_size = Vector2(132, 126)
compact_minimum_size = Vector2(118, 110)
minimum_font_size = 15
maximum_font_size = 25
maximum_font_size = 22
motion_phase = 0.2
[node name="WorldLegible" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
@ -258,12 +258,12 @@ custom_minimum_size = Vector2(0, 0)
mouse_filter = 2
focus_mode = 0
text = "ui\npixelation\nretro"
neutral_size = Vector2(160, 150)
neutral_size = Vector2(140, 130)
desktop_anchor = Vector2(560, 240)
compact_anchor = Vector2(545, 210)
compact_minimum_size = Vector2(132, 126)
compact_minimum_size = Vector2(118, 110)
minimum_font_size = 15
maximum_font_size = 25
maximum_font_size = 22
motion_phase = 3.55
[node name="UILegible" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
@ -332,6 +332,30 @@ minimum_font_size = 12
maximum_font_size = 13
motion_phase = 0.55
[node name="ChatDock" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "chat dock\nleft"
neutral_size = Vector2(96, 96)
desktop_anchor = Vector2(190, 435)
compact_anchor = Vector2(160, 378)
compact_minimum_size = Vector2(82, 82)
minimum_font_size = 12
maximum_font_size = 16
motion_phase = 1.1
[node name="PaintDock" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
text = "paint dock\nright"
neutral_size = Vector2(96, 96)
desktop_anchor = Vector2(530, 435)
compact_anchor = Vector2(448, 378)
compact_minimum_size = Vector2(82, 82)
minimum_font_size = 12
maximum_font_size = 16
motion_phase = 1.65
[node name="DisplayBackButton" parent="DisplayPage/BubbleCluster" instance=ExtResource("4_bubble")]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 0)
@ -342,7 +366,7 @@ compact_anchor = Vector2(315, 390)
compact_minimum_size = Vector2(82, 78)
minimum_font_size = 13
maximum_font_size = 16
motion_phase = 1.1
motion_phase = 2.2
[node name="ControlsPage" type="Control" parent="."]
unique_name_in_owner = true

View file

@ -1,48 +1,94 @@
class_name SurfaceDrawingToolbar
extends PanelContainer
extends Control
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
const TOOLBAR_WIDTH: float = 580.0
const TOOLBAR_HEIGHT: float = 373.0
const COLOR_RAIL_WIDTH: float = 46.0
@onready var _mode_button: Button = %ModeButton
@onready var _brush_option: OptionButton = %BrushOption
@onready var _grid_option: OptionButton = %GridOption
@onready var _color_list: VBoxContainer = %ColorList
@onready var _top_panel: PanelContainer = %TopPanel
@onready var _color_panel: PanelContainer = %ColorPanel
@onready var _eraser_button: Button = %EraserButton
@onready var _undo_button: Button = %UndoButton
@onready var _hide_guide_button: Button = %HideGuideButton
@onready var _restore_guide_button: Button = %RestoreGuideButton
@onready var _finalize_guide_button: Button = %FinalizeGuideButton
@onready var _close_button: Button = %CloseButton
var _service: NetworkSurfaceDrawingService
var _unlocks: PlayerArtUnlocks
var _color_buttons: Dictionary[StringName, Button] = {}
var _dock_right: bool = true
func _ready() -> void:
UtilityPageStyleType.apply_page(self)
var panel: StyleBoxFlat = UtilityPageStyleType.panel_style()
panel.content_margin_left = 10.0
panel.content_margin_top = 10.0
panel.content_margin_right = 10.0
panel.content_margin_bottom = 10.0
add_theme_stylebox_override("panel", panel)
_apply_toolbar_panel(_top_panel)
_apply_toolbar_panel(_color_panel)
UtilityPageStyleType.apply_ocean_button(_mode_button)
UtilityPageStyleType.apply_ocean_button(_brush_option)
UtilityPageStyleType.apply_ocean_button(_grid_option)
for button: Button in _action_buttons():
UtilityPageStyleType.apply_ocean_button(button)
button.custom_minimum_size = Vector2(48, 44)
_make_button_round(button, 22)
_mode_button.custom_minimum_size = Vector2(48, 48)
_make_mode_button_round()
_make_button_round(_mode_button, 24)
_mode_button.pressed.connect(_toggle_mode)
_eraser_button.pressed.connect(_toggle_eraser)
_undo_button.pressed.connect(_undo_last_stroke)
_hide_guide_button.pressed.connect(
_arm_guide_action.bind(NetworkSurfaceDrawingService.GuideAction.HIDE)
)
_restore_guide_button.pressed.connect(
_arm_guide_action.bind(NetworkSurfaceDrawingService.GuideAction.RESTORE)
)
_finalize_guide_button.pressed.connect(
_arm_guide_action.bind(NetworkSurfaceDrawingService.GuideAction.FINALIZE)
)
_close_button.pressed.connect(_close_toolbar)
_brush_option.item_selected.connect(_select_brush)
_grid_option.item_selected.connect(_select_grid)
_build_options()
_apply_dock_side()
hide()
func _make_mode_button_round() -> void:
func _apply_toolbar_panel(panel: PanelContainer) -> void:
var style: StyleBoxFlat = UtilityPageStyleType.panel_style()
style.content_margin_left = 6.0
style.content_margin_top = 6.0
style.content_margin_right = 6.0
style.content_margin_bottom = 6.0
style.set_corner_radius_all(10)
panel.add_theme_stylebox_override("panel", style)
func _action_buttons() -> Array[Button]:
return [
_eraser_button,
_undo_button,
_hide_guide_button,
_restore_guide_button,
_finalize_guide_button,
_close_button,
]
func _make_button_round(button: Button, radius: int) -> void:
for style_name: StringName in [
&"normal", &"hover", &"pressed", &"focus", &"disabled",
]:
var existing: StyleBox = _mode_button.get_theme_stylebox(style_name)
var existing: StyleBox = button.get_theme_stylebox(style_name)
var style := existing.duplicate() as StyleBoxFlat
if style == null:
continue
style.set_corner_radius_all(24)
_mode_button.add_theme_stylebox_override(style_name, style)
style.set_corner_radius_all(radius)
button.add_theme_stylebox_override(style_name, style)
func setup(
@ -62,13 +108,42 @@ func setup(
_refresh_unlocks()
func set_dock_right(should_dock_right: bool) -> void:
_dock_right = should_dock_right
if is_node_ready():
_apply_dock_side()
func is_docked_right() -> bool:
return _dock_right
func _apply_dock_side() -> void:
anchor_left = 1.0 if _dock_right else 0.0
anchor_right = anchor_left
offset_left = -TOOLBAR_WIDTH if _dock_right else 0.0
offset_right = 0.0 if _dock_right else TOOLBAR_WIDTH
offset_top = 0.0
offset_bottom = TOOLBAR_HEIGHT
_color_panel.offset_left = (
TOOLBAR_WIDTH - COLOR_RAIL_WIDTH if _dock_right else 0.0
)
_color_panel.offset_right = (
TOOLBAR_WIDTH if _dock_right else COLOR_RAIL_WIDTH
)
func owns_pointer_event(event: InputEvent) -> bool:
if not visible:
return false
if _brush_option.get_popup().visible or _grid_option.get_popup().visible:
return true
if event is InputEventMouse:
return get_global_rect().has_point((event as InputEventMouse).position)
var pointer_position: Vector2 = (event as InputEventMouse).position
return (
_top_panel.get_global_rect().has_point(pointer_position)
or _color_panel.get_global_rect().has_point(pointer_position)
)
return false
@ -114,7 +189,11 @@ func _refresh_unlocks() -> void:
_apply_color_button_style(
button,
color_id,
_service != null and _service.get_color_id() == color_id,
(
_service != null
and not _service.is_eraser_mode()
and _service.get_color_id() == color_id
),
)
@ -161,6 +240,26 @@ func _select_color(color_id: StringName) -> void:
_service.set_color_id(color_id)
func _toggle_eraser() -> void:
if _service != null:
_service.set_eraser_mode(not _service.is_eraser_mode())
func _undo_last_stroke() -> void:
if _service != null:
_service.request_undo_last_stroke()
func _arm_guide_action(action: int) -> void:
if _service != null:
_service.arm_guide_action(action)
func _close_toolbar() -> void:
if _service != null:
_service.deactivate()
func _on_unlocks_changed(_unlock_mask: int) -> void:
_refresh_unlocks()
@ -185,9 +284,26 @@ func _on_service_state_changed(
)
_select_option_by_id(_brush_option, brush_size)
_select_option_by_id(_grid_option, grid_size)
_refresh_action_state()
_refresh_unlocks()
func _refresh_action_state() -> void:
if _service == null:
return
_eraser_button.button_pressed = _service.is_eraser_mode()
var guide_action: int = _service.get_armed_guide_action()
_hide_guide_button.button_pressed = (
guide_action == NetworkSurfaceDrawingService.GuideAction.HIDE
)
_restore_guide_button.button_pressed = (
guide_action == NetworkSurfaceDrawingService.GuideAction.RESTORE
)
_finalize_guide_button.button_pressed = (
guide_action == NetworkSurfaceDrawingService.GuideAction.FINALIZE
)
func _select_option_by_id(option: OptionButton, item_id: int) -> void:
for index: int in range(option.item_count):
if option.get_item_id(index) == item_id:

View file

@ -3,55 +3,97 @@
[ext_resource type="Script" path="res://ui/surface_drawing_toolbar.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
[node name="SurfaceDrawingToolbar" type="PanelContainer"]
[node name="SurfaceDrawingToolbar" type="Control"]
visible = false
z_index = 85
layout_mode = 0
offset_left = 18.0
offset_top = 18.0
offset_right = 284.0
offset_bottom = 340.0
mouse_filter = 0
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -580.0
offset_bottom = 373.0
grow_horizontal = 0
mouse_filter = 2
theme = ExtResource("2_theme")
script = ExtResource("1_script")
[node name="Layout" type="HBoxContainer" parent="."]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ColorList" type="VBoxContainer" parent="Layout"]
[node name="TopPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
layout_mode = 0
offset_right = 580.0
offset_bottom = 60.0
[node name="Top" type="HBoxContainer" parent="TopPanel"]
layout_mode = 2
theme_override_constants/separation = 5
[node name="Tools" type="VBoxContainer" parent="Layout"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8
[node name="Top" type="HBoxContainer" parent="Layout/Tools"]
layout_mode = 2
theme_override_constants/separation = 6
[node name="ModeButton" type="Button" parent="Layout/Tools/Top"]
[node name="ModeButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "switch between grid and marker mode"
text = "▦"
[node name="BrushOption" type="OptionButton" parent="Layout/Tools/Top"]
[node name="BrushOption" type="OptionButton" parent="TopPanel/Top"]
unique_name_in_owner = true
custom_minimum_size = Vector2(80, 48)
layout_mode = 2
tooltip_text = "brush size"
[node name="GridOption" type="OptionButton" parent="Layout/Tools/Top"]
[node name="GridOption" type="OptionButton" parent="TopPanel/Top"]
unique_name_in_owner = true
custom_minimum_size = Vector2(92, 48)
layout_mode = 2
tooltip_text = "grid size"
[node name="Help" type="Label" parent="Layout/Tools"]
[node name="EraserButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
text = "click place/draw\nshift click hide/erase\nctrl z undo\nshift scroll zoom"
theme_override_colors/font_color = Color(0.623529, 0.811765, 0.823529, 1)
theme_override_font_sizes/font_size = 14
tooltip_text = "toggle eraser"
toggle_mode = true
text = "⌫"
[node name="UndoButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "undo last stroke"
text = "↶"
[node name="HideGuideButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "hide grid on next click"
toggle_mode = true
text = "◌"
[node name="RestoreGuideButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "restore hidden grid on next click"
toggle_mode = true
text = "▤"
[node name="FinalizeGuideButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "finish grid on next click"
toggle_mode = true
text = "✓"
[node name="CloseButton" type="Button" parent="TopPanel/Top"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "close art tools"
text = "×"
[node name="ColorPanel" type="PanelContainer" parent="."]
unique_name_in_owner = true
layout_mode = 0
offset_left = 534.0
offset_top = 54.0
offset_right = 580.0
offset_bottom = 373.0
[node name="ColorList" type="VBoxContainer" parent="ColorPanel"]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 5