forked from woofmeow/straywild
Add gathering, unified inventory, and real-time world
This commit is contained in:
parent
173371e6fc
commit
fa57edca83
113 changed files with 6700 additions and 1307 deletions
159
ui/components/general_inventory_grid.gd
Normal file
159
ui/components/general_inventory_grid.gd
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
class_name GeneralInventoryGrid
|
||||
extends Control
|
||||
|
||||
signal entry_selected(kind: int, identity: StringName)
|
||||
signal slot_activated(slot: GeneralInventorySlot)
|
||||
signal context_requested(slot: GeneralInventorySlot)
|
||||
signal context_changed(slot: GeneralInventorySlot, text: String, active: bool)
|
||||
|
||||
const DEFAULT_SLOT_SIZE := Vector2(52.0, 52.0)
|
||||
const DEFAULT_SLOT_SEPARATION: int = 5
|
||||
const COLUMNS: int = PlayerInventoryLayout.INVENTORY_COLUMNS
|
||||
|
||||
var _layout: PlayerInventoryLayout
|
||||
var _bag: PlayerBag
|
||||
var _fish_inventory: FishInventory
|
||||
var _hotbar: PlayerHotbar
|
||||
var _item_catalog: ItemCatalog
|
||||
var _container: int = PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
var _grid: GridContainer
|
||||
var _slots: Array[GeneralInventorySlot] = []
|
||||
var _slot_size: Vector2 = DEFAULT_SLOT_SIZE
|
||||
var _slot_separation: int = DEFAULT_SLOT_SEPARATION
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_grid = GridContainer.new()
|
||||
_grid.columns = COLUMNS
|
||||
_apply_grid_separation()
|
||||
add_child(_grid)
|
||||
|
||||
|
||||
func set_slot_presentation(slot_size: Vector2, separation: int) -> void:
|
||||
_slot_size = Vector2(maxf(1.0, slot_size.x), maxf(1.0, slot_size.y))
|
||||
_slot_separation = maxi(0, separation)
|
||||
if _grid != null:
|
||||
_apply_grid_separation()
|
||||
if is_node_ready() and _layout != null:
|
||||
var active_capacity := (
|
||||
_layout.get_inventory_capacity()
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else _layout.get_storage_capacity()
|
||||
)
|
||||
_rebuild(
|
||||
PlayerInventoryLayout.MAX_INVENTORY_SLOT_COUNT
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else PlayerInventoryLayout.MAX_STORAGE_SLOT_COUNT,
|
||||
active_capacity,
|
||||
)
|
||||
|
||||
|
||||
func setup(
|
||||
layout: PlayerInventoryLayout,
|
||||
bag: PlayerBag,
|
||||
fish_inventory: FishInventory,
|
||||
hotbar: PlayerHotbar,
|
||||
item_catalog: ItemCatalog,
|
||||
container: int,
|
||||
) -> void:
|
||||
_layout = layout
|
||||
_bag = bag
|
||||
_fish_inventory = fish_inventory
|
||||
_hotbar = hotbar
|
||||
_item_catalog = item_catalog
|
||||
_container = container
|
||||
if _layout != null and not _layout.layout_changed.is_connected(refresh):
|
||||
_layout.layout_changed.connect(refresh)
|
||||
refresh()
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
if not is_node_ready() or _layout == null:
|
||||
return
|
||||
var active_capacity := (
|
||||
_layout.get_inventory_capacity()
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else _layout.get_storage_capacity()
|
||||
)
|
||||
var visible_capacity := (
|
||||
PlayerInventoryLayout.MAX_INVENTORY_SLOT_COUNT
|
||||
if _container == PlayerInventoryLayout.InventoryContainer.INVENTORY
|
||||
else PlayerInventoryLayout.MAX_STORAGE_SLOT_COUNT
|
||||
)
|
||||
if _slots.size() != visible_capacity:
|
||||
_rebuild(visible_capacity, active_capacity)
|
||||
else:
|
||||
for index: int in _slots.size():
|
||||
_slots[index].set_locked(index >= active_capacity)
|
||||
for slot: GeneralInventorySlot in _slots:
|
||||
slot.refresh()
|
||||
|
||||
|
||||
func get_slots() -> Array[GeneralInventorySlot]:
|
||||
var active: Array[GeneralInventorySlot] = []
|
||||
for slot: GeneralInventorySlot in _slots:
|
||||
if not slot.disabled:
|
||||
active.append(slot)
|
||||
return active
|
||||
|
||||
|
||||
func get_first_occupied_slot() -> GeneralInventorySlot:
|
||||
for slot: GeneralInventorySlot in _slots:
|
||||
if not slot.entry_identity.is_empty():
|
||||
return slot
|
||||
return _slots.front() if not _slots.is_empty() else null
|
||||
|
||||
|
||||
func _rebuild(visible_capacity: int, active_capacity: int) -> void:
|
||||
for child: Node in _grid.get_children():
|
||||
child.queue_free()
|
||||
_slots.clear()
|
||||
for slot_index: int in visible_capacity:
|
||||
var slot := GeneralInventorySlot.new()
|
||||
slot.custom_minimum_size = _slot_size
|
||||
slot.entry_selected.connect(
|
||||
func(kind: int, identity: StringName) -> void:
|
||||
entry_selected.emit(kind, identity)
|
||||
)
|
||||
slot.pressed.connect(func() -> void: slot_activated.emit(slot))
|
||||
slot.context_requested.connect(
|
||||
func(source: GeneralInventorySlot) -> void:
|
||||
context_requested.emit(source)
|
||||
)
|
||||
slot.context_changed.connect(
|
||||
func(
|
||||
source: GeneralInventorySlot,
|
||||
text: String,
|
||||
active: bool,
|
||||
) -> void:
|
||||
context_changed.emit(source, text, active)
|
||||
)
|
||||
_grid.add_child(slot)
|
||||
slot.set_presentation_size(_slot_size)
|
||||
slot.configure(
|
||||
slot_index,
|
||||
_container,
|
||||
slot_index >= active_capacity,
|
||||
_layout,
|
||||
_bag,
|
||||
_fish_inventory,
|
||||
_hotbar,
|
||||
_item_catalog,
|
||||
)
|
||||
_slots.append(slot)
|
||||
custom_minimum_size = Vector2(
|
||||
float(COLUMNS) * _slot_size.x
|
||||
+ float(COLUMNS - 1) * _slot_separation,
|
||||
ceilf(float(visible_capacity) / float(COLUMNS)) * _slot_size.y
|
||||
+ maxf(
|
||||
0.0,
|
||||
ceilf(float(visible_capacity) / float(COLUMNS)) - 1.0,
|
||||
) * _slot_separation,
|
||||
)
|
||||
size = custom_minimum_size
|
||||
ControllerFocusNavigation.configure_spatial_neighbors(_slots)
|
||||
|
||||
|
||||
func _apply_grid_separation() -> void:
|
||||
_grid.add_theme_constant_override("h_separation", _slot_separation)
|
||||
_grid.add_theme_constant_override("v_separation", _slot_separation)
|
||||
1
ui/components/general_inventory_grid.gd.uid
Normal file
1
ui/components/general_inventory_grid.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://k6h255orqr6o
|
||||
346
ui/components/general_inventory_slot.gd
Normal file
346
ui/components/general_inventory_slot.gd
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
class_name GeneralInventorySlot
|
||||
extends Button
|
||||
|
||||
signal entry_selected(kind: int, identity: StringName)
|
||||
signal entry_moved
|
||||
signal context_requested(slot: GeneralInventorySlot)
|
||||
signal context_changed(slot: GeneralInventorySlot, text: String, active: bool)
|
||||
|
||||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
const LOCK_ICON: Texture2D = preload("res://ui/icons/pictograms/lock_light.png")
|
||||
|
||||
var slot_index: int = -1
|
||||
var container: int = -1
|
||||
var entry_kind: int = -1
|
||||
var entry_identity: StringName
|
||||
var _locked: bool = false
|
||||
|
||||
var _layout: PlayerInventoryLayout
|
||||
var _bag: PlayerBag
|
||||
var _fish_inventory: FishInventory
|
||||
var _hotbar: PlayerHotbar
|
||||
var _item_catalog: ItemCatalog
|
||||
var _icon: TextureRect
|
||||
var _quantity: Label
|
||||
var _presentation_size := Vector2(52.0, 52.0)
|
||||
var _context_text: String = ""
|
||||
var _context_hovered: bool = false
|
||||
var _context_focused: bool = false
|
||||
var _staged: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
focus_mode = Control.FOCUS_ALL
|
||||
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
pressed.connect(_on_pressed)
|
||||
focus_entered.connect(_set_context_focused.bind(true))
|
||||
focus_exited.connect(_set_context_focused.bind(false))
|
||||
mouse_entered.connect(_set_context_hovered.bind(true))
|
||||
mouse_exited.connect(_set_context_hovered.bind(false))
|
||||
gui_input.connect(_on_gui_input)
|
||||
_icon = TextureRect.new()
|
||||
_icon.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_icon)
|
||||
_quantity = Label.new()
|
||||
_quantity.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
_quantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
_quantity.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_quantity.add_theme_color_override(
|
||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_quantity.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_quantity)
|
||||
_apply_presentation()
|
||||
|
||||
|
||||
func set_presentation_size(presentation_size: Vector2) -> void:
|
||||
_presentation_size = presentation_size
|
||||
custom_minimum_size = presentation_size
|
||||
if is_node_ready():
|
||||
_apply_presentation()
|
||||
|
||||
|
||||
func configure(
|
||||
new_slot_index: int,
|
||||
new_container: int,
|
||||
is_locked: bool,
|
||||
layout: PlayerInventoryLayout,
|
||||
bag: PlayerBag,
|
||||
fish_inventory: FishInventory,
|
||||
hotbar: PlayerHotbar,
|
||||
item_catalog: ItemCatalog,
|
||||
) -> void:
|
||||
slot_index = new_slot_index
|
||||
container = new_container
|
||||
_locked = is_locked
|
||||
_layout = layout
|
||||
_bag = bag
|
||||
_fish_inventory = fish_inventory
|
||||
_hotbar = hotbar
|
||||
_item_catalog = item_catalog
|
||||
name = "InventorySlot%d" % slot_index
|
||||
refresh()
|
||||
|
||||
|
||||
func set_locked(is_locked: bool) -> void:
|
||||
if _locked == is_locked:
|
||||
return
|
||||
_locked = is_locked
|
||||
refresh()
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
call_deferred("_update_context_presence")
|
||||
entry_kind = -1
|
||||
entry_identity = StringName()
|
||||
_context_text = ""
|
||||
_icon.texture = null
|
||||
_quantity.text = ""
|
||||
disabled = _locked
|
||||
_apply_icon_geometry()
|
||||
if _locked:
|
||||
_icon.texture = LOCK_ICON
|
||||
_icon.modulate = Color(UtilityPageStyle.OCEAN_DISABLED, 0.18)
|
||||
var storage_slot := (
|
||||
container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
)
|
||||
_context_text = (
|
||||
"locked storage slot" if storage_slot else "locked backpack slot"
|
||||
)
|
||||
tooltip_text = ""
|
||||
accessibility_name = "%s %d" % [_context_text, slot_index + 1]
|
||||
return
|
||||
_icon.modulate = Color.WHITE
|
||||
_context_text = "empty slot"
|
||||
tooltip_text = ""
|
||||
accessibility_name = "empty %s slot %d" % [
|
||||
"storage"
|
||||
if container == PlayerInventoryLayout.InventoryContainer.STORAGE
|
||||
else "inventory",
|
||||
slot_index + 1,
|
||||
]
|
||||
if _layout == null:
|
||||
return
|
||||
var entry := _layout.get_entry_at(container, slot_index)
|
||||
if entry.is_empty():
|
||||
return
|
||||
entry_kind = int(entry.get("kind", -1))
|
||||
entry_identity = StringName(str(entry.get("identity", "")))
|
||||
if entry_kind == PlayerInventoryLayout.EntryKind.ITEM:
|
||||
var item: ItemDataType = (
|
||||
_item_catalog.get_item_by_id(entry_identity)
|
||||
if _item_catalog != null else null
|
||||
)
|
||||
if item == null:
|
||||
return
|
||||
_icon.texture = item.icon
|
||||
var quantity := _bag.get_quantity(entry_identity) if _bag != null else 0
|
||||
_quantity.text = "×%d" % quantity if quantity > 1 else ""
|
||||
_context_text = _item_context_text(item, quantity)
|
||||
accessibility_name = "%s, slot %d" % [item.display_name, slot_index + 1]
|
||||
elif entry_kind == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
var fish_catch = (
|
||||
_fish_inventory.get_catch_by_id(entry_identity)
|
||||
if _fish_inventory != null else null
|
||||
)
|
||||
if fish_catch == null:
|
||||
return
|
||||
_icon.texture = fish_catch.fish.display_texture
|
||||
_context_text = _catch_context_text(fish_catch)
|
||||
var catch_name: String = FishQuality.qualified_name(
|
||||
fish_catch.fish.display_name, fish_catch.quality
|
||||
)
|
||||
accessibility_name = "%s, slot %d" % [catch_name, slot_index + 1]
|
||||
|
||||
|
||||
func _on_pressed() -> void:
|
||||
if _locked:
|
||||
return
|
||||
entry_selected.emit(entry_kind, entry_identity)
|
||||
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
var mouse_event := event as InputEventMouseButton
|
||||
if (
|
||||
mouse_event != null
|
||||
and mouse_event.button_index == MOUSE_BUTTON_RIGHT
|
||||
and mouse_event.pressed
|
||||
and not _locked
|
||||
and not entry_identity.is_empty()
|
||||
):
|
||||
context_requested.emit(self)
|
||||
accept_event()
|
||||
|
||||
|
||||
func _set_context_hovered(active: bool) -> void:
|
||||
_context_hovered = active
|
||||
_update_context_presence()
|
||||
|
||||
|
||||
func _set_context_focused(active: bool) -> void:
|
||||
_context_focused = active
|
||||
_update_context_presence()
|
||||
|
||||
|
||||
func _update_context_presence() -> void:
|
||||
if _locked or entry_identity.is_empty():
|
||||
context_changed.emit(self, "", false)
|
||||
return
|
||||
var active: bool = _context_hovered or _context_focused
|
||||
context_changed.emit(self, _context_text if active else "", active)
|
||||
|
||||
|
||||
func set_move_source(active: bool) -> void:
|
||||
modulate = Color(1.0, 1.0, 1.0, 0.42) if active else Color.WHITE
|
||||
|
||||
|
||||
func set_staged(active: bool) -> void:
|
||||
if _staged == active:
|
||||
return
|
||||
_staged = active
|
||||
_apply_style()
|
||||
|
||||
|
||||
func _item_context_text(item: ItemDataType, quantity: int) -> String:
|
||||
var lines: Array[String] = [item.display_name]
|
||||
lines.append("%s • quantity %d" % [item.get_category_name(), quantity])
|
||||
if item.equippable:
|
||||
lines.append("equippable")
|
||||
elif item.usable:
|
||||
lines.append("usable")
|
||||
if not item.description.strip_edges().is_empty():
|
||||
lines.append(item.description.strip_edges())
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
func _catch_context_text(fish_catch: FishCatch) -> String:
|
||||
var catch_name: String = FishQuality.qualified_name(
|
||||
fish_catch.fish.display_name,
|
||||
fish_catch.quality,
|
||||
)
|
||||
return "%s\n%0.2f lb • value %d\n%s" % [
|
||||
catch_name,
|
||||
fish_catch.weight_lb,
|
||||
fish_catch.sale_value,
|
||||
fish_catch.fish.logbook_fact,
|
||||
]
|
||||
|
||||
|
||||
func _get_drag_data(_at_position: Vector2) -> Variant:
|
||||
if _locked or entry_identity.is_empty() or _icon.texture == null:
|
||||
return null
|
||||
var preview := TextureRect.new()
|
||||
preview.custom_minimum_size = Vector2(56.0, 56.0)
|
||||
preview.texture = _icon.texture
|
||||
preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
preview.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
set_drag_preview(preview)
|
||||
if entry_kind == PlayerInventoryLayout.EntryKind.CATCH:
|
||||
return {
|
||||
"kind": "cooler_fish",
|
||||
"catch_id": String(entry_identity),
|
||||
}
|
||||
return {
|
||||
"kind": "bag_item",
|
||||
"item_id": String(entry_identity),
|
||||
}
|
||||
|
||||
|
||||
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
|
||||
if _locked or typeof(data) != TYPE_DICTIONARY or _layout == null:
|
||||
return false
|
||||
var kind := str((data as Dictionary).get("kind", ""))
|
||||
return kind in ["bag_item", "cooler_fish", "hotbar_slot"]
|
||||
|
||||
|
||||
func _drop_data(_at_position: Vector2, data: Variant) -> void:
|
||||
if not _can_drop_data(Vector2.ZERO, data):
|
||||
return
|
||||
var payload := data as Dictionary
|
||||
var payload_kind := str(payload.get("kind", ""))
|
||||
var moved: bool = false
|
||||
if payload_kind == "hotbar_slot":
|
||||
moved = (
|
||||
_hotbar != null
|
||||
and _hotbar.move_slot_to_container(
|
||||
int(payload.get("slot_index", -1)),
|
||||
container,
|
||||
slot_index,
|
||||
)
|
||||
)
|
||||
else:
|
||||
var kind := (
|
||||
PlayerInventoryLayout.EntryKind.CATCH
|
||||
if payload_kind == "cooler_fish"
|
||||
else PlayerInventoryLayout.EntryKind.ITEM
|
||||
)
|
||||
var identity := StringName(str(
|
||||
payload.get(
|
||||
"catch_id" if payload_kind == "cooler_fish" else "item_id",
|
||||
"",
|
||||
)
|
||||
))
|
||||
moved = _layout.move_entry(kind, identity, container, slot_index)
|
||||
if moved:
|
||||
entry_moved.emit()
|
||||
|
||||
|
||||
func _apply_style() -> void:
|
||||
var radius: int = roundi(minf(_presentation_size.x, _presentation_size.y) * 0.5)
|
||||
var normal := UtilityPageStyle.rounded_style(
|
||||
Color(
|
||||
UtilityPageStyle.OCEAN_SELECTED
|
||||
if _staged else UtilityPageStyle.OCEAN_FIELD,
|
||||
0.92 if _staged else 0.88,
|
||||
),
|
||||
radius,
|
||||
)
|
||||
var hover := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_SELECTED, 0.92), radius
|
||||
)
|
||||
var locked := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_FIELD, 0.38), radius
|
||||
)
|
||||
for state: StringName in [&"normal", &"pressed"]:
|
||||
add_theme_stylebox_override(state, normal)
|
||||
add_theme_stylebox_override("disabled", locked)
|
||||
for state: StringName in [&"hover", &"focus"]:
|
||||
add_theme_stylebox_override(state, hover)
|
||||
|
||||
|
||||
func _apply_presentation() -> void:
|
||||
_apply_icon_geometry()
|
||||
var is_large: bool = _presentation_size.x >= 70.0
|
||||
var quantity_width: float = 42.0 if is_large else 34.0
|
||||
var quantity_height: float = 20.0 if is_large else 17.0
|
||||
_quantity.position = Vector2(
|
||||
-quantity_width - (8.0 if is_large else 5.0),
|
||||
-quantity_height - (6.0 if is_large else 4.0),
|
||||
)
|
||||
_quantity.size = Vector2(quantity_width, quantity_height)
|
||||
_quantity.add_theme_font_size_override("font_size", 14 if is_large else 11)
|
||||
_apply_style()
|
||||
|
||||
|
||||
func _apply_icon_geometry() -> void:
|
||||
var is_large: bool = _presentation_size.x >= 70.0
|
||||
var margin: float
|
||||
if _locked:
|
||||
var lock_size: float = 24.0 if is_large else 18.0
|
||||
margin = maxf(
|
||||
(_presentation_size.x - lock_size) * 0.5,
|
||||
0.0,
|
||||
)
|
||||
else:
|
||||
margin = 10.0 if is_large else 7.0
|
||||
_icon.offset_left = margin
|
||||
_icon.offset_top = margin
|
||||
_icon.offset_right = -margin
|
||||
_icon.offset_bottom = -margin
|
||||
1
ui/components/general_inventory_slot.gd.uid
Normal file
1
ui/components/general_inventory_slot.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cgaypm0vokkup
|
||||
|
|
@ -6,7 +6,10 @@ const CONTENT_MARGIN_LEFT := 18.0
|
|||
const CONTENT_MARGIN_TOP := 98.0
|
||||
const CONTENT_MARGIN_RIGHT := 18.0
|
||||
const CONTENT_MARGIN_BOTTOM := 16.0
|
||||
# Retained for the Logbook's separate handwritten presentation. Inventory
|
||||
# notepads deliberately prioritize legibility with Tuffy.
|
||||
const HANDWRITTEN_FONT: Font = preload("res://ui/fonts/seattle_avenue.otf")
|
||||
const NOTEPAD_FONT: Font = preload("res://ui/fonts/Tuffy_Bold.otf")
|
||||
const NOTEPAD_TEXTURE: Texture2D = preload("res://art/ui/ui_notepad.png")
|
||||
const NOTEPAD_CANONICAL_OVERSCAN: float = 1.2
|
||||
const NOTEPAD_ART_OFFSET := Vector2(10.0, 20.0)
|
||||
|
|
@ -24,11 +27,11 @@ func _ready() -> void:
|
|||
|
||||
|
||||
static func apply_handwritten_to(root: Control) -> void:
|
||||
root.add_theme_font_override("font", HANDWRITTEN_FONT)
|
||||
root.add_theme_font_override("font", NOTEPAD_FONT)
|
||||
for descendant: Node in root.find_children("*", "Control", true, false):
|
||||
var control := descendant as Control
|
||||
control.add_theme_font_override(
|
||||
"font", HANDWRITTEN_FONT
|
||||
"font", NOTEPAD_FONT
|
||||
)
|
||||
if control is Label:
|
||||
control.add_theme_color_override("font_color", INK_COLOR)
|
||||
|
|
@ -37,7 +40,7 @@ static func apply_handwritten_to(root: Control) -> void:
|
|||
func _draw() -> void:
|
||||
draw_texture_rect(NOTEPAD_TEXTURE, get_art_rect(), false)
|
||||
|
||||
var font: Font = HANDWRITTEN_FONT
|
||||
var font: Font = NOTEPAD_FONT
|
||||
draw_string(
|
||||
font,
|
||||
Vector2(20.0, 58.0),
|
||||
|
|
|
|||
71
ui/components/shop_sale_tray_slot.gd
Normal file
71
ui/components/shop_sale_tray_slot.gd
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
class_name ShopSaleTraySlot
|
||||
extends Button
|
||||
|
||||
signal remove_requested(key: String)
|
||||
signal drop_requested(payload: Dictionary)
|
||||
|
||||
var entry_key: String = ""
|
||||
var _icon: TextureRect
|
||||
var _quantity: Label
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
custom_minimum_size = GeneralInventoryGrid.DEFAULT_SLOT_SIZE
|
||||
focus_mode = Control.FOCUS_ALL
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
pressed.connect(func() -> void: remove_requested.emit(entry_key))
|
||||
_icon = TextureRect.new()
|
||||
_icon.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_icon.offset_left = 6.0
|
||||
_icon.offset_top = 4.0
|
||||
_icon.offset_right = -6.0
|
||||
_icon.offset_bottom = -4.0
|
||||
_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_icon)
|
||||
_quantity = Label.new()
|
||||
_quantity.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
_quantity.position = Vector2(-34.0, -20.0)
|
||||
_quantity.size = Vector2(30.0, 16.0)
|
||||
_quantity.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
_quantity.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_quantity)
|
||||
var normal := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_FIELD, 0.96), 26
|
||||
)
|
||||
var hover := UtilityPageStyle.rounded_style(
|
||||
Color(UtilityPageStyle.OCEAN_SELECTED, 0.96), 26
|
||||
)
|
||||
for state: StringName in [&"normal", &"pressed", &"disabled"]:
|
||||
add_theme_stylebox_override(state, normal)
|
||||
for state: StringName in [&"hover", &"focus"]:
|
||||
add_theme_stylebox_override(state, hover)
|
||||
|
||||
|
||||
func configure(
|
||||
key: String,
|
||||
icon: Texture2D,
|
||||
label: String,
|
||||
quantity: int = 1,
|
||||
) -> void:
|
||||
entry_key = key
|
||||
_icon.texture = icon
|
||||
_quantity.text = "×%d" % quantity if quantity > 1 else ""
|
||||
tooltip_text = "%s · select to remove" % label
|
||||
accessibility_name = tooltip_text
|
||||
|
||||
|
||||
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
|
||||
return (
|
||||
typeof(data) == TYPE_DICTIONARY
|
||||
and str((data as Dictionary).get("kind", "")) in [
|
||||
"bag_item", "cooler_fish"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
func _drop_data(_at_position: Vector2, data: Variant) -> void:
|
||||
if _can_drop_data(Vector2.ZERO, data):
|
||||
drop_requested.emit((data as Dictionary).duplicate(true))
|
||||
1
ui/components/shop_sale_tray_slot.gd.uid
Normal file
1
ui/components/shop_sale_tray_slot.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://w87v5n2xm64p
|
||||
Loading…
Add table
Add a link
Reference in a new issue