forked from woofmeow/straywild
feat: add universal controller focus pointer
This commit is contained in:
parent
41308768b7
commit
a99916c38b
11 changed files with 719 additions and 115 deletions
|
|
@ -12,6 +12,7 @@ func _initialize() -> void:
|
|||
|
||||
|
||||
func _run() -> void:
|
||||
root.size = Vector2i(1280, 720)
|
||||
var stage := Control.new()
|
||||
stage.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
root.add_child(stage)
|
||||
|
|
@ -19,14 +20,20 @@ func _run() -> void:
|
|||
stage.add_child(presentation)
|
||||
var standard_button := Button.new()
|
||||
standard_button.text = "standard"
|
||||
standard_button.position = Vector2(80.0, 60.0)
|
||||
standard_button.custom_minimum_size = Vector2(120.0, 60.0)
|
||||
var normal_style := _button_style(Color("123f4e"))
|
||||
var old_focus_style := _button_style(Color("238697"))
|
||||
standard_button.add_theme_stylebox_override("normal", normal_style)
|
||||
standard_button.add_theme_stylebox_override("focus", old_focus_style)
|
||||
stage.add_child(standard_button)
|
||||
var authored_selector := Button.new()
|
||||
authored_selector.text = "authored selector"
|
||||
authored_selector.position = Vector2(140.0, 0.0)
|
||||
authored_selector.custom_minimum_size = Vector2(160.0, 60.0)
|
||||
authored_selector.set_meta(&"controller_focus_inversion_disabled", true)
|
||||
stage.add_child(authored_selector)
|
||||
var second_button := Button.new()
|
||||
second_button.text = "second"
|
||||
second_button.position = Vector2(260.0, 60.0)
|
||||
second_button.custom_minimum_size = Vector2(120.0, 60.0)
|
||||
# Legacy opt-outs must not prevent the new universal cursor from appearing.
|
||||
second_button.set_meta(&"controller_focus_inversion_disabled", true)
|
||||
stage.add_child(second_button)
|
||||
await process_frame
|
||||
|
||||
var controller_event := InputEventJoypadButton.new()
|
||||
|
|
@ -35,42 +42,198 @@ func _run() -> void:
|
|||
presentation._input(controller_event)
|
||||
standard_button.grab_focus()
|
||||
await process_frame
|
||||
var focus_arrow := presentation.get("_focus_arrow") as TextureRect
|
||||
_expect(focus_arrow != null, "controller focus arrow was not created")
|
||||
_expect(focus_arrow.visible, "controller focus arrow is not visible")
|
||||
_expect(
|
||||
standard_button.material != null,
|
||||
"ordinary controller focus receives inversion",
|
||||
focus_arrow.texture == FocusPresentationType.FOCUS_ARROW_TEXTURE,
|
||||
"controller focus arrow uses the wrong artwork",
|
||||
)
|
||||
_expect(
|
||||
is_equal_approx(
|
||||
focus_arrow.rotation_degrees,
|
||||
FocusPresentationType.FOCUS_ARROW_ROTATION_DEGREES,
|
||||
),
|
||||
"controller focus arrow does not point toward five o'clock",
|
||||
)
|
||||
_expect(
|
||||
standard_button.material == null,
|
||||
"controller focus still applies material inversion",
|
||||
)
|
||||
_expect(
|
||||
standard_button.get_theme_stylebox("focus") == normal_style,
|
||||
"native focus highlighting was not neutralized",
|
||||
)
|
||||
_expect(
|
||||
_arrow_marks_top_left(focus_arrow, standard_button.get_global_rect()),
|
||||
"controller focus arrow is not in the control's top-left corner",
|
||||
)
|
||||
stage.scale = Vector2.ONE * 0.5
|
||||
await process_frame
|
||||
_expect(
|
||||
focus_arrow.size.is_equal_approx(
|
||||
FocusPresentationType.FOCUS_ARROW_SIZE * 0.5
|
||||
),
|
||||
"controller focus arrow does not follow canonical UI stage scale",
|
||||
)
|
||||
_expect(
|
||||
_arrow_marks_top_left(focus_arrow, standard_button.get_global_rect()),
|
||||
"scaled controller focus arrow left its control's top-left corner",
|
||||
)
|
||||
stage.scale = Vector2.ONE
|
||||
await process_frame
|
||||
|
||||
second_button.grab_focus()
|
||||
await process_frame
|
||||
_expect(
|
||||
focus_arrow.visible,
|
||||
"legacy inversion metadata incorrectly hides the focus arrow",
|
||||
)
|
||||
_expect(
|
||||
_arrow_marks_top_left(focus_arrow, second_button.get_global_rect()),
|
||||
"controller focus arrow did not follow the focused control",
|
||||
)
|
||||
|
||||
var pointer_motion := InputEventMouseMotion.new()
|
||||
pointer_motion.position = Vector2(4.0, 4.0)
|
||||
presentation._input(pointer_motion)
|
||||
await process_frame
|
||||
_expect(
|
||||
standard_button.material == null,
|
||||
"mouse motion clears controller focus presentation",
|
||||
not focus_arrow.visible,
|
||||
"mouse motion does not clear controller focus presentation",
|
||||
)
|
||||
_expect(
|
||||
standard_button.get_theme_stylebox("focus") == old_focus_style,
|
||||
"native theme overrides were not restored after controller use",
|
||||
)
|
||||
presentation._input(controller_event)
|
||||
|
||||
authored_selector.grab_focus()
|
||||
var item_list := ItemList.new()
|
||||
item_list.position = Vector2(80.0, 160.0)
|
||||
item_list.size = Vector2(240.0, 150.0)
|
||||
item_list.max_columns = 1
|
||||
for index: int in 4:
|
||||
item_list.add_item("list item %d" % index)
|
||||
stage.add_child(item_list)
|
||||
await process_frame
|
||||
item_list.select(0)
|
||||
item_list.grab_focus()
|
||||
await process_frame
|
||||
var first_item_arrow_y: float = focus_arrow.position.y
|
||||
item_list.select(2)
|
||||
await process_frame
|
||||
_expect(
|
||||
standard_button.material == null,
|
||||
"inversion clears when focus moves",
|
||||
focus_arrow.position.y > first_item_arrow_y,
|
||||
"focus arrow does not follow the selected ItemList row",
|
||||
)
|
||||
_expect(
|
||||
authored_selector.material == null,
|
||||
"authored selector backgrounds opt out of inversion",
|
||||
item_list.get_theme_stylebox("selected") is StyleBoxEmpty,
|
||||
"ItemList still draws a selected-row cursor highlight",
|
||||
)
|
||||
_expect(
|
||||
item_list.get_theme_color("font_selected_color")
|
||||
== item_list.get_theme_color("font_color"),
|
||||
"ItemList still recolors the selected-row cursor text",
|
||||
)
|
||||
|
||||
standard_button.grab_focus()
|
||||
var tree := Tree.new()
|
||||
tree.position = Vector2(700.0, 160.0)
|
||||
tree.size = Vector2(240.0, 150.0)
|
||||
tree.hide_root = true
|
||||
var tree_root: TreeItem = tree.create_item()
|
||||
var first_tree_item: TreeItem = tree.create_item(tree_root)
|
||||
first_tree_item.set_text(0, "first folder")
|
||||
var second_tree_item: TreeItem = tree.create_item(tree_root)
|
||||
second_tree_item.set_text(0, "second folder")
|
||||
stage.add_child(tree)
|
||||
await process_frame
|
||||
standard_button.focus_mode = Control.FOCUS_NONE
|
||||
first_tree_item.select(0)
|
||||
tree.grab_focus()
|
||||
await process_frame
|
||||
var first_tree_arrow_y: float = focus_arrow.position.y
|
||||
second_tree_item.select(0)
|
||||
await process_frame
|
||||
_expect(
|
||||
standard_button.material == null,
|
||||
"inversion clears when a focused control is defocused",
|
||||
focus_arrow.position.y > first_tree_arrow_y,
|
||||
"focus arrow does not follow the selected Tree row",
|
||||
)
|
||||
|
||||
var popup := PopupMenu.new()
|
||||
for index: int in 5:
|
||||
popup.add_item("popup item %d" % index)
|
||||
root.add_child(popup)
|
||||
popup.popup(Rect2i(500, 300, 0, 0))
|
||||
popup.set_focused_item(0)
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
_expect(
|
||||
presentation.get("_focused_popup") == popup,
|
||||
"focus arrow did not enter an open PopupMenu",
|
||||
)
|
||||
_expect(
|
||||
presentation.get("_focus_layer").custom_viewport == popup,
|
||||
"focus arrow is not rendered inside the PopupMenu viewport",
|
||||
)
|
||||
_expect(
|
||||
popup.get_theme_stylebox("hover") is StyleBoxEmpty,
|
||||
"PopupMenu still draws its native focus highlight",
|
||||
)
|
||||
_expect(
|
||||
_arrow_marks_top_left(
|
||||
focus_arrow,
|
||||
presentation.call("_popup_focus_target_rect", popup) as Rect2,
|
||||
),
|
||||
"focus arrow does not mark the focused PopupMenu item",
|
||||
)
|
||||
var first_popup_arrow_y: float = focus_arrow.position.y
|
||||
popup.set_focused_item(2)
|
||||
await process_frame
|
||||
_expect(
|
||||
focus_arrow.position.y > first_popup_arrow_y,
|
||||
"focus arrow does not follow PopupMenu item navigation",
|
||||
)
|
||||
popup.hide()
|
||||
popup.queue_free()
|
||||
await process_frame
|
||||
item_list.grab_focus()
|
||||
await process_frame
|
||||
|
||||
var dialog := FileDialog.new()
|
||||
dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
dialog.size = Vector2i(720, 480)
|
||||
root.add_child(dialog)
|
||||
dialog.popup_centered()
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
var dialog_cancel := dialog.get_cancel_button()
|
||||
dialog_cancel.grab_focus()
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
_expect(
|
||||
focus_arrow.visible,
|
||||
"focus arrow is hidden for an embedded dialog control",
|
||||
)
|
||||
_expect(
|
||||
presentation.get("_focus_layer").custom_viewport
|
||||
== dialog_cancel.get_viewport(),
|
||||
"focus arrow is not rendered inside the dialog viewport",
|
||||
)
|
||||
_expect(
|
||||
_arrow_marks_top_left(
|
||||
focus_arrow,
|
||||
presentation.call("_focus_target_rect", dialog_cancel) as Rect2,
|
||||
),
|
||||
"focus arrow does not follow an embedded dialog control",
|
||||
)
|
||||
dialog.hide()
|
||||
dialog.queue_free()
|
||||
await process_frame
|
||||
item_list.grab_focus()
|
||||
await process_frame
|
||||
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.position = Vector2(0.0, 80.0)
|
||||
scroll.position = Vector2(420.0, 160.0)
|
||||
scroll.size = Vector2(180.0, 90.0)
|
||||
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||
stage.add_child(scroll)
|
||||
|
|
@ -86,15 +249,29 @@ func _run() -> void:
|
|||
last_scroll_button = scroll_button
|
||||
await process_frame
|
||||
last_scroll_button.grab_focus()
|
||||
await process_frame
|
||||
await process_frame
|
||||
for _frame: int in 2:
|
||||
await process_frame
|
||||
_expect(
|
||||
scroll.scroll_vertical > 0,
|
||||
"controller focus scrolls an off-screen selection into view",
|
||||
"controller focus does not reveal an off-screen selection",
|
||||
)
|
||||
_expect(
|
||||
root.gui_get_focus_owner() == last_scroll_button,
|
||||
"scroll following preserves the selected controller control",
|
||||
"scroll following does not preserve the focused control",
|
||||
)
|
||||
_expect(
|
||||
_arrow_marks_top_left(
|
||||
focus_arrow,
|
||||
last_scroll_button.get_global_rect(),
|
||||
),
|
||||
"focus arrow does not follow a scrolled control",
|
||||
)
|
||||
|
||||
last_scroll_button.focus_mode = Control.FOCUS_NONE
|
||||
await process_frame
|
||||
_expect(
|
||||
not focus_arrow.visible,
|
||||
"focus arrow remains after its control becomes unfocusable",
|
||||
)
|
||||
|
||||
stage.queue_free()
|
||||
|
|
@ -107,6 +284,30 @@ func _run() -> void:
|
|||
quit(1)
|
||||
|
||||
|
||||
func _button_style(color: Color) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = color
|
||||
style.set_corner_radius_all(8)
|
||||
return style
|
||||
|
||||
|
||||
func _arrow_marks_top_left(arrow: Control, target: Rect2) -> bool:
|
||||
var arrow_pivot: Vector2 = arrow.position + arrow.size * 0.5
|
||||
var arrow_scale: Vector2 = arrow.size / FocusPresentationType.FOCUS_ARROW_SIZE
|
||||
var tip_from_pivot: Vector2 = (
|
||||
(FocusPresentationType.FOCUS_ARROW_TIP_UV - Vector2.ONE * 0.5)
|
||||
* arrow.size
|
||||
).rotated(deg_to_rad(
|
||||
FocusPresentationType.FOCUS_ARROW_ROTATION_DEGREES
|
||||
))
|
||||
var actual_tip: Vector2 = arrow_pivot + tip_from_pivot
|
||||
var expected_tip: Vector2 = (
|
||||
target.position
|
||||
+ FocusPresentationType.FOCUS_ARROW_TARGET_OVERLAP * arrow_scale
|
||||
)
|
||||
return actual_tip.distance_to(expected_tip) <= 0.5
|
||||
|
||||
|
||||
func _expect(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
_failures.append(message)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ var _dragging: bool = false
|
|||
func _ready() -> void:
|
||||
mouse_entered.connect(_set_hovered.bind(true))
|
||||
mouse_exited.connect(_set_hovered.bind(false))
|
||||
focus_entered.connect(_refresh_style)
|
||||
focus_exited.connect(_refresh_style)
|
||||
resized.connect(_update_visual_pivot)
|
||||
_update_visual_pivot()
|
||||
_apply_quantity_style()
|
||||
|
|
@ -60,7 +58,7 @@ func advance_presentation(elapsed: float, motion_enabled: bool) -> void:
|
|||
) * 0.014
|
||||
_visual_root.position = Vector2(0.0, bob)
|
||||
_visual_root.rotation = tilt
|
||||
var emphasis: float = 1.04 if _hovered or has_focus() else 1.0
|
||||
var emphasis: float = 1.04 if _hovered else 1.0
|
||||
_visual_root.scale = Vector2.ONE * _depth_scale * emphasis
|
||||
|
||||
|
||||
|
|
@ -103,9 +101,7 @@ func _refresh_style() -> void:
|
|||
)
|
||||
if _selected:
|
||||
style.bg_color = UtilityPageStyle.OCEAN_SELECTED
|
||||
if has_focus():
|
||||
style.bg_color = UtilityPageStyle.OCEAN_BUTTON_HOVER
|
||||
elif _hovered:
|
||||
if _hovered:
|
||||
style.bg_color = Color(UtilityPageStyle.OCEAN_PANEL_MID, 0.88)
|
||||
style.set_border_width_all(0)
|
||||
style.set_corner_radius_all(64)
|
||||
|
|
|
|||
|
|
@ -28,14 +28,11 @@ var neutral_position: Vector2 = Vector2.ZERO
|
|||
var presented_size: Vector2 = Vector2.ZERO
|
||||
var emphasis: float = 0.0
|
||||
var _hovered: bool = false
|
||||
var _focused: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_entered.connect(_set_hovered.bind(true))
|
||||
mouse_exited.connect(_set_hovered.bind(false))
|
||||
focus_entered.connect(_set_focused.bind(true))
|
||||
focus_exited.connect(_set_focused.bind(false))
|
||||
resized.connect(_update_pivot)
|
||||
_update_pivot()
|
||||
apply_profile()
|
||||
|
|
@ -151,7 +148,7 @@ func _apply_icon_presentation(bubble_size: Vector2) -> void:
|
|||
|
||||
|
||||
func advance_emphasis(delta: float) -> void:
|
||||
var target: float = 1.0 if _hovered or _focused else 0.0
|
||||
var target: float = 1.0 if _hovered else 0.0
|
||||
emphasis = move_toward(
|
||||
emphasis,
|
||||
target,
|
||||
|
|
@ -233,10 +230,6 @@ func _set_hovered(value: bool) -> void:
|
|||
_hovered = value
|
||||
|
||||
|
||||
func _set_focused(value: bool) -> void:
|
||||
_focused = value
|
||||
|
||||
|
||||
func _update_pivot() -> void:
|
||||
pivot_offset = size * 0.5
|
||||
|
||||
|
|
@ -247,12 +240,12 @@ func apply_profile() -> void:
|
|||
add_theme_stylebox_override("normal", profile.make_normal_style())
|
||||
var hover_style: StyleBoxFlat = profile.make_hover_style()
|
||||
add_theme_stylebox_override("hover", hover_style)
|
||||
add_theme_stylebox_override("focus", hover_style)
|
||||
add_theme_stylebox_override("focus", profile.make_normal_style())
|
||||
add_theme_stylebox_override("pressed", profile.make_pressed_style())
|
||||
add_theme_stylebox_override("disabled", profile.make_disabled_style())
|
||||
add_theme_color_override("font_color", profile.text_color)
|
||||
add_theme_color_override("font_hover_color", profile.text_hover_color)
|
||||
add_theme_color_override("font_focus_color", profile.text_hover_color)
|
||||
add_theme_color_override("font_focus_color", profile.text_color)
|
||||
add_theme_color_override("font_pressed_color", profile.text_pressed_color)
|
||||
add_theme_color_override("font_disabled_color", profile.text_disabled_color)
|
||||
var label_control: Control = get_label_control()
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ var _quality_color := Color.WHITE
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
set_meta(&"controller_focus_inversion_disabled", true)
|
||||
resized.connect(_update_visual_pivot)
|
||||
focus_entered.connect(_refresh_style)
|
||||
focus_exited.connect(_refresh_style)
|
||||
mouse_entered.connect(func() -> void:
|
||||
_hovered = true
|
||||
_refresh_style()
|
||||
|
|
@ -108,7 +105,7 @@ func advance_presentation(delta: float, elapsed: float) -> void:
|
|||
var interaction_lift: float = 0.0
|
||||
if _batch_selected:
|
||||
interaction_lift += 3.0
|
||||
if _focused_catch or has_focus():
|
||||
if _focused_catch:
|
||||
interaction_lift += 1.5
|
||||
elif _hovered:
|
||||
interaction_lift += 0.8
|
||||
|
|
@ -117,7 +114,7 @@ func advance_presentation(delta: float, elapsed: float) -> void:
|
|||
var emphasis: float = 1.0
|
||||
if _batch_selected:
|
||||
emphasis += 0.085
|
||||
if _focused_catch or has_focus():
|
||||
if _focused_catch:
|
||||
emphasis += 0.035
|
||||
elif _hovered:
|
||||
emphasis += 0.045
|
||||
|
|
@ -154,7 +151,7 @@ func _refresh_style() -> void:
|
|||
var circle_visible: bool = (
|
||||
_batch_selected
|
||||
or _hovered
|
||||
or has_focus()
|
||||
or _focused_catch
|
||||
)
|
||||
var base_style: StyleBox = idle
|
||||
if circle_visible:
|
||||
|
|
@ -168,7 +165,7 @@ func _refresh_style() -> void:
|
|||
base_style,
|
||||
)
|
||||
add_theme_stylebox_override("hover", selected if _batch_selected else hover)
|
||||
add_theme_stylebox_override("focus", selected)
|
||||
add_theme_stylebox_override("focus", base_style)
|
||||
add_theme_stylebox_override("pressed", selected)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,15 +16,12 @@ extends Button
|
|||
@onready var _ink_outline: NotepadInkOutline = %InkOutline
|
||||
|
||||
var _hovered: bool = false
|
||||
var _focused: bool = false
|
||||
var _pressed: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_entered.connect(_set_hovered.bind(true))
|
||||
mouse_exited.connect(_set_hovered.bind(false))
|
||||
focus_entered.connect(_set_focused.bind(true))
|
||||
focus_exited.connect(_set_focused.bind(false))
|
||||
button_down.connect(_set_pressed.bind(true))
|
||||
button_up.connect(_set_pressed.bind(false))
|
||||
gui_input.connect(_on_gui_input)
|
||||
|
|
@ -42,11 +39,6 @@ func _set_hovered(value: bool) -> void:
|
|||
_refresh_ink()
|
||||
|
||||
|
||||
func _set_focused(value: bool) -> void:
|
||||
_focused = value
|
||||
_refresh_ink()
|
||||
|
||||
|
||||
func _set_pressed(value: bool) -> void:
|
||||
_pressed = value
|
||||
_refresh_ink()
|
||||
|
|
@ -75,7 +67,7 @@ func _refresh_ink() -> void:
|
|||
if not disabled:
|
||||
if persistent_mark:
|
||||
strength = 0.78
|
||||
if _hovered or _focused:
|
||||
if _hovered:
|
||||
strength = maxf(strength, 0.92)
|
||||
if _pressed:
|
||||
strength = 1.0
|
||||
|
|
@ -99,6 +91,6 @@ func _apply_text_color() -> void:
|
|||
var color: Color = disabled_ink_color if disabled else ink_color
|
||||
add_theme_color_override("font_color", color)
|
||||
add_theme_color_override("font_hover_color", color.darkened(0.08))
|
||||
add_theme_color_override("font_focus_color", color.darkened(0.08))
|
||||
add_theme_color_override("font_focus_color", color)
|
||||
add_theme_color_override("font_pressed_color", color.darkened(0.16))
|
||||
add_theme_color_override("font_disabled_color", disabled_ink_color)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ var _items: Array[String] = []
|
|||
var _item_ids: Array[int] = []
|
||||
var _selected_index: int = 0
|
||||
var _hovered: bool = false
|
||||
var _focused: bool = false
|
||||
var _opened_with_mouse: bool = false
|
||||
|
||||
|
||||
|
|
@ -43,8 +42,6 @@ func _ready() -> void:
|
|||
_apply_outline_geometry()
|
||||
mouse_entered.connect(_set_hovered.bind(true))
|
||||
mouse_exited.connect(_set_hovered.bind(false))
|
||||
focus_entered.connect(_set_focused.bind(true))
|
||||
focus_exited.connect(_set_focused.bind(false))
|
||||
gui_input.connect(_on_display_gui_input)
|
||||
for index: int in _choice_buttons.size():
|
||||
var button: Button = _choice_buttons[index]
|
||||
|
|
@ -202,15 +199,10 @@ func _set_hovered(value: bool) -> void:
|
|||
_refresh_ink()
|
||||
|
||||
|
||||
func _set_focused(value: bool) -> void:
|
||||
_focused = value
|
||||
_refresh_ink()
|
||||
|
||||
|
||||
func _refresh_ink() -> void:
|
||||
if is_node_ready():
|
||||
_ink_outline.set_mark_strength(
|
||||
0.88 if (_hovered or _focused) and not disabled else 0.0
|
||||
0.88 if _hovered and not disabled else 0.0
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -246,9 +238,9 @@ func _apply_paper_style() -> void:
|
|||
for state: StringName in [
|
||||
&"hover",
|
||||
&"pressed",
|
||||
&"focus",
|
||||
]:
|
||||
button.add_theme_stylebox_override(state, hover)
|
||||
button.add_theme_stylebox_override("focus", empty)
|
||||
button.add_theme_color_override("font_color", ink_color)
|
||||
button.add_theme_color_override(
|
||||
"font_hover_color",
|
||||
|
|
@ -256,7 +248,7 @@ func _apply_paper_style() -> void:
|
|||
)
|
||||
button.add_theme_color_override(
|
||||
"font_focus_color",
|
||||
ink_color.darkened(0.08),
|
||||
ink_color,
|
||||
)
|
||||
button.add_theme_color_override(
|
||||
"font_pressed_color",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ var _selected: bool = false
|
|||
|
||||
func _ready() -> void:
|
||||
toggle_mode = true
|
||||
set_meta(&"controller_focus_inversion_disabled", true)
|
||||
_selected = button_pressed
|
||||
add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||
add_theme_font_size_override("font_size", FONT_SIZE)
|
||||
|
|
@ -43,8 +42,6 @@ func _ready() -> void:
|
|||
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")
|
||||
|
|
@ -113,7 +110,7 @@ func _set_hovered(hovered: bool) -> void:
|
|||
func _target_y() -> float:
|
||||
if button_pressed:
|
||||
return SELECTED_SETTLE_Y
|
||||
if _hovered or has_focus():
|
||||
if _hovered:
|
||||
return HOVER_SETTLE_Y
|
||||
return INACTIVE_SETTLE_Y
|
||||
|
||||
|
|
@ -139,8 +136,6 @@ func _draw() -> void:
|
|||
)
|
||||
if disabled:
|
||||
base_color = base_color.lerp(Color("8a978f"), 0.62)
|
||||
elif has_focus():
|
||||
base_color = base_color.lightened(0.12)
|
||||
elif _hovered:
|
||||
base_color = base_color.lightened(0.08)
|
||||
var width: float = size.x
|
||||
|
|
|
|||
|
|
@ -2,34 +2,51 @@ class_name ControllerFocusPresentation
|
|||
extends Node
|
||||
|
||||
const CONTROLLER_MOTION_THRESHOLD: float = 0.35
|
||||
const INVERSION_DISABLED_META: StringName = &"controller_focus_inversion_disabled"
|
||||
const FOCUS_ARROW_TEXTURE: Texture2D = preload(
|
||||
"res://ui/icons/pictograms/arrow_light_up_full.png"
|
||||
)
|
||||
const FOCUS_ARROW_SIZE: Vector2 = Vector2(32.0, 32.0)
|
||||
const FOCUS_ARROW_ROTATION_DEGREES: float = 150.0
|
||||
const FOCUS_ARROW_TIP_UV: Vector2 = Vector2(0.5, 0.0)
|
||||
const FOCUS_ARROW_TARGET_OVERLAP: Vector2 = Vector2(10.0, 10.0)
|
||||
const FOCUS_ARROW_CANVAS_LAYER: int = 120
|
||||
|
||||
const FOCUS_STYLE_REPLACEMENTS: Dictionary[StringName, StringName] = {
|
||||
&"focus": &"normal",
|
||||
&"selected": &"",
|
||||
&"selected_focus": &"",
|
||||
&"cursor": &"",
|
||||
&"cursor_unfocused": &"",
|
||||
}
|
||||
const FOCUS_COLOR_REPLACEMENTS: Dictionary[StringName, StringName] = {
|
||||
&"font_focus_color": &"font_color",
|
||||
&"icon_focus_color": &"icon_normal_color",
|
||||
&"font_selected_color": &"font_color",
|
||||
&"font_hovered_selected_color": &"font_hovered_color",
|
||||
}
|
||||
|
||||
var _controller_active: bool = false
|
||||
var _focused_item: CanvasItem
|
||||
var _original_material: Material
|
||||
var _inversion_material: ShaderMaterial
|
||||
var _focused_control: Control
|
||||
var _focused_popup: PopupMenu
|
||||
var _popup_scroll_offset: float = 0.0
|
||||
var _suppressed_control: Control
|
||||
var _original_style_overrides: Dictionary[StringName, Dictionary] = {}
|
||||
var _original_color_overrides: Dictionary[StringName, Dictionary] = {}
|
||||
var _original_popup_hover_style: Dictionary = {}
|
||||
var _original_popup_hover_color: Dictionary = {}
|
||||
var _focus_layer: CanvasLayer
|
||||
var _focus_arrow: TextureRect
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var inversion_shader := Shader.new()
|
||||
inversion_shader.code = """
|
||||
shader_type canvas_item;
|
||||
render_mode unshaded;
|
||||
|
||||
void fragment() {
|
||||
vec4 source = texture(TEXTURE, UV) * COLOR;
|
||||
COLOR = vec4(vec3(1.0) - source.rgb, source.a);
|
||||
}
|
||||
"""
|
||||
_inversion_material = ShaderMaterial.new()
|
||||
_inversion_material.shader = inversion_shader
|
||||
_build_focus_arrow()
|
||||
get_viewport().gui_focus_changed.connect(_on_focus_changed)
|
||||
set_process_input(true)
|
||||
set_process(true)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_restore_focused_item()
|
||||
_clear_focus_presentation()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
|
@ -52,31 +69,65 @@ func _input(event: InputEvent) -> void:
|
|||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not is_instance_valid(_focused_item):
|
||||
_focused_item = null
|
||||
_original_material = null
|
||||
return
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if _controller_active:
|
||||
var focused_popup: PopupMenu = _active_focused_popup(get_viewport())
|
||||
if focused_popup != null:
|
||||
if focused_popup != _focused_popup or _focused_control != null:
|
||||
_apply_to_popup(focused_popup)
|
||||
else:
|
||||
_update_arrow_geometry()
|
||||
return
|
||||
var focus_owner: Control = _active_focus_owner()
|
||||
if (
|
||||
focus_owner != _focused_item
|
||||
or not focus_owner.is_visible_in_tree()
|
||||
or focus_owner.focus_mode == Control.FOCUS_NONE
|
||||
or bool(focus_owner.get_meta(INVERSION_DISABLED_META, false))
|
||||
focus_owner != _focused_control
|
||||
or not _focus_is_presentable(focus_owner)
|
||||
):
|
||||
_apply_to_focus(focus_owner)
|
||||
elif _controller_active:
|
||||
_update_arrow_geometry()
|
||||
|
||||
|
||||
func _build_focus_arrow() -> void:
|
||||
_focus_layer = CanvasLayer.new()
|
||||
_focus_layer.name = "ControllerFocusArrowLayer"
|
||||
_focus_layer.layer = FOCUS_ARROW_CANVAS_LAYER
|
||||
add_child(_focus_layer)
|
||||
_focus_arrow = TextureRect.new()
|
||||
_focus_arrow.name = "ControllerFocusArrow"
|
||||
_focus_arrow.texture = FOCUS_ARROW_TEXTURE
|
||||
_focus_arrow.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_focus_arrow.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_focus_arrow.size = FOCUS_ARROW_SIZE
|
||||
_focus_arrow.pivot_offset = FOCUS_ARROW_SIZE * 0.5
|
||||
_focus_arrow.rotation_degrees = FOCUS_ARROW_ROTATION_DEGREES
|
||||
_focus_arrow.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_focus_arrow.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_focus_arrow.focus_mode = Control.FOCUS_NONE
|
||||
_focus_arrow.visible = false
|
||||
_focus_layer.add_child(_focus_arrow)
|
||||
|
||||
|
||||
func _set_controller_active(active: bool) -> void:
|
||||
if _controller_active == active:
|
||||
return
|
||||
_controller_active = active
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if _controller_active:
|
||||
var focused_popup: PopupMenu = _active_focused_popup(get_viewport())
|
||||
if focused_popup != null:
|
||||
_apply_to_popup(focused_popup)
|
||||
return
|
||||
var focus_owner: Control = _active_focus_owner()
|
||||
_apply_to_focus(focus_owner)
|
||||
if _controller_active:
|
||||
_queue_focus_visibility_update(focus_owner)
|
||||
|
||||
|
||||
func _on_focus_changed(control: Control) -> void:
|
||||
if _controller_active:
|
||||
var focused_popup: PopupMenu = _active_focused_popup(get_viewport())
|
||||
if focused_popup != null:
|
||||
_apply_to_popup(focused_popup)
|
||||
return
|
||||
_apply_to_focus(control)
|
||||
if _controller_active:
|
||||
_queue_focus_visibility_update(control)
|
||||
|
|
@ -100,22 +151,370 @@ func _ensure_focus_visible(control: Control) -> void:
|
|||
|
||||
|
||||
func _apply_to_focus(control: Control) -> void:
|
||||
_restore_focused_item()
|
||||
_clear_focus_presentation()
|
||||
if not _controller_active or not _focus_is_presentable(control):
|
||||
return
|
||||
_focused_control = control
|
||||
_focus_layer.custom_viewport = control.get_viewport()
|
||||
_suppress_native_focus_highlight(control)
|
||||
_focus_arrow.visible = true
|
||||
_update_arrow_geometry()
|
||||
|
||||
|
||||
func _apply_to_popup(popup: PopupMenu) -> void:
|
||||
_clear_focus_presentation()
|
||||
if (
|
||||
not _controller_active
|
||||
or control == null
|
||||
or not control.is_visible_in_tree()
|
||||
or control.focus_mode == Control.FOCUS_NONE
|
||||
or bool(control.get_meta(INVERSION_DISABLED_META, false))
|
||||
or popup == null
|
||||
or not is_instance_valid(popup)
|
||||
or not popup.visible
|
||||
or popup.get_focused_item() < 0
|
||||
):
|
||||
return
|
||||
_focused_item = control
|
||||
_original_material = _focused_item.material
|
||||
_focused_item.material = _inversion_material
|
||||
_focused_popup = popup
|
||||
_popup_scroll_offset = 0.0
|
||||
_focus_layer.custom_viewport = popup
|
||||
_suppress_popup_focus_highlight(popup)
|
||||
_focus_arrow.visible = true
|
||||
_update_arrow_geometry()
|
||||
|
||||
|
||||
func _restore_focused_item() -> void:
|
||||
if is_instance_valid(_focused_item):
|
||||
_focused_item.material = _original_material
|
||||
_focused_item = null
|
||||
_original_material = null
|
||||
func _focus_is_presentable(control: Control) -> bool:
|
||||
return (
|
||||
control != null
|
||||
and is_instance_valid(control)
|
||||
and control.is_visible_in_tree()
|
||||
and control.focus_mode != Control.FOCUS_NONE
|
||||
)
|
||||
|
||||
|
||||
func _update_arrow_geometry() -> void:
|
||||
if (
|
||||
_focus_arrow == null
|
||||
or not _controller_active
|
||||
or (
|
||||
_focused_popup == null
|
||||
and not _focus_is_presentable(_focused_control)
|
||||
)
|
||||
or (
|
||||
_focused_popup != null
|
||||
and (
|
||||
not is_instance_valid(_focused_popup)
|
||||
or not _focused_popup.visible
|
||||
or _focused_popup.get_focused_item() < 0
|
||||
)
|
||||
)
|
||||
):
|
||||
if _focus_arrow != null:
|
||||
_focus_arrow.visible = false
|
||||
return
|
||||
var focus_rect: Rect2 = (
|
||||
_popup_focus_target_rect(_focused_popup)
|
||||
if _focused_popup != null
|
||||
else _focus_target_rect(_focused_control)
|
||||
)
|
||||
var presentation_scale: Vector2 = _presentation_canvas_scale()
|
||||
var arrow_size: Vector2 = FOCUS_ARROW_SIZE * presentation_scale
|
||||
_focus_arrow.size = arrow_size
|
||||
_focus_arrow.pivot_offset = arrow_size * 0.5
|
||||
var desired_tip: Vector2 = (
|
||||
focus_rect.position
|
||||
+ FOCUS_ARROW_TARGET_OVERLAP * presentation_scale
|
||||
)
|
||||
var tip_from_pivot: Vector2 = (
|
||||
(FOCUS_ARROW_TIP_UV - Vector2.ONE * 0.5) * arrow_size
|
||||
).rotated(deg_to_rad(FOCUS_ARROW_ROTATION_DEGREES))
|
||||
_focus_arrow.position = (
|
||||
desired_tip - arrow_size * 0.5 - tip_from_pivot
|
||||
)
|
||||
_focus_arrow.visible = true
|
||||
|
||||
|
||||
func _presentation_canvas_scale() -> Vector2:
|
||||
var host := get_parent() as Control
|
||||
if host == null:
|
||||
return Vector2.ONE
|
||||
var host_scale: Vector2 = (
|
||||
host.get_global_transform_with_canvas().get_scale().abs()
|
||||
)
|
||||
return Vector2(
|
||||
maxf(host_scale.x, 0.001),
|
||||
maxf(host_scale.y, 0.001),
|
||||
)
|
||||
|
||||
|
||||
func _focus_target_rect(control: Control) -> Rect2:
|
||||
var item_list := control as ItemList
|
||||
if item_list != null:
|
||||
var selected_items: PackedInt32Array = item_list.get_selected_items()
|
||||
if not selected_items.is_empty():
|
||||
return _rect_in_presentation_viewport(
|
||||
item_list,
|
||||
item_list.get_item_rect(selected_items[0]),
|
||||
)
|
||||
var tree := control as Tree
|
||||
if tree != null:
|
||||
var selected_item: TreeItem = tree.get_selected()
|
||||
if selected_item != null:
|
||||
return _rect_in_presentation_viewport(
|
||||
tree,
|
||||
tree.get_item_area_rect(
|
||||
selected_item,
|
||||
tree.get_selected_column(),
|
||||
),
|
||||
)
|
||||
return _rect_in_presentation_viewport(
|
||||
control,
|
||||
Rect2(Vector2.ZERO, control.size),
|
||||
)
|
||||
|
||||
|
||||
func _rect_in_presentation_viewport(
|
||||
control: Control,
|
||||
local_rect: Rect2,
|
||||
) -> Rect2:
|
||||
var canvas_transform: Transform2D = control.get_global_transform_with_canvas()
|
||||
var corners: Array[Vector2] = [
|
||||
canvas_transform * local_rect.position,
|
||||
canvas_transform * Vector2(local_rect.end.x, local_rect.position.y),
|
||||
canvas_transform * local_rect.end,
|
||||
canvas_transform * Vector2(local_rect.position.x, local_rect.end.y),
|
||||
]
|
||||
var result := Rect2(corners.front(), Vector2.ZERO)
|
||||
for corner: Vector2 in corners:
|
||||
result = result.expand(corner)
|
||||
return _offset_rect_to_presentation_viewport(
|
||||
result,
|
||||
control.get_viewport(),
|
||||
)
|
||||
|
||||
|
||||
func _popup_focus_target_rect(popup: PopupMenu) -> Rect2:
|
||||
var focused_index: int = popup.get_focused_item()
|
||||
var panel_style: StyleBox = popup.get_theme_stylebox(&"panel")
|
||||
var panel_left: float = panel_style.get_content_margin(SIDE_LEFT)
|
||||
var panel_top: float = panel_style.get_content_margin(SIDE_TOP)
|
||||
var panel_right: float = panel_style.get_content_margin(SIDE_RIGHT)
|
||||
var panel_bottom: float = panel_style.get_content_margin(SIDE_BOTTOM)
|
||||
var item_heights: Array[float] = []
|
||||
var focused_top: float = 0.0
|
||||
for index: int in popup.get_item_count():
|
||||
var item_height: float = _popup_item_height(popup, index)
|
||||
item_heights.append(item_height)
|
||||
if index < focused_index:
|
||||
focused_top += item_height
|
||||
var focused_height: float = item_heights[focused_index]
|
||||
var visible_height: float = maxf(
|
||||
1.0,
|
||||
float(popup.size.y) - panel_top - panel_bottom,
|
||||
)
|
||||
if focused_top < _popup_scroll_offset:
|
||||
_popup_scroll_offset = focused_top
|
||||
elif focused_top + focused_height > _popup_scroll_offset + visible_height:
|
||||
_popup_scroll_offset = (
|
||||
focused_top + focused_height - visible_height
|
||||
)
|
||||
var local_rect := Rect2(
|
||||
Vector2(
|
||||
panel_left,
|
||||
panel_top + focused_top - _popup_scroll_offset,
|
||||
),
|
||||
Vector2(
|
||||
maxf(1.0, float(popup.size.x) - panel_left - panel_right),
|
||||
focused_height,
|
||||
),
|
||||
)
|
||||
if _focus_layer.custom_viewport == popup:
|
||||
return local_rect
|
||||
local_rect.position += Vector2(popup.position)
|
||||
var parent_node: Node = popup.get_parent()
|
||||
if parent_node == null:
|
||||
return local_rect
|
||||
return _offset_rect_to_presentation_viewport(
|
||||
local_rect,
|
||||
parent_node.get_viewport(),
|
||||
)
|
||||
|
||||
|
||||
func _popup_item_height(popup: PopupMenu, index: int) -> float:
|
||||
var vertical_separation: float = float(
|
||||
popup.get_theme_constant(&"v_separation")
|
||||
)
|
||||
if popup.is_item_separator(index) and popup.get_item_text(index).is_empty():
|
||||
return maxf(
|
||||
1.0,
|
||||
popup.get_theme_stylebox(&"separator").get_minimum_size().y,
|
||||
) + vertical_separation
|
||||
var font: Font = popup.get_theme_font(&"font")
|
||||
var font_height: float = font.get_height(
|
||||
popup.get_theme_font_size(&"font_size")
|
||||
)
|
||||
var icon: Texture2D = popup.get_item_icon(index)
|
||||
var icon_height: float = icon.get_height() if icon != null else 0.0
|
||||
return maxf(font_height, icon_height) + vertical_separation
|
||||
|
||||
|
||||
func _offset_rect_to_presentation_viewport(
|
||||
rect: Rect2,
|
||||
starting_viewport: Viewport,
|
||||
) -> Rect2:
|
||||
var result: Rect2 = rect
|
||||
var current_viewport: Viewport = starting_viewport
|
||||
var presentation_viewport: Viewport = (
|
||||
_focus_layer.custom_viewport
|
||||
if _focus_layer != null
|
||||
else get_viewport()
|
||||
)
|
||||
while current_viewport != presentation_viewport:
|
||||
var embedded_window := current_viewport as Window
|
||||
if embedded_window == null or not embedded_window.is_embedded():
|
||||
break
|
||||
result.position += Vector2(embedded_window.position)
|
||||
var parent_node: Node = embedded_window.get_parent()
|
||||
if parent_node == null:
|
||||
break
|
||||
current_viewport = parent_node.get_viewport()
|
||||
return result
|
||||
|
||||
|
||||
func _active_focus_owner() -> Control:
|
||||
return _focus_owner_in_viewport(get_viewport())
|
||||
|
||||
|
||||
func _active_focused_popup(viewport: Viewport) -> PopupMenu:
|
||||
var embedded_windows: Array[Window] = viewport.get_embedded_subwindows()
|
||||
for index: int in range(embedded_windows.size() - 1, -1, -1):
|
||||
var window: Window = embedded_windows[index]
|
||||
if not window.visible:
|
||||
continue
|
||||
var nested_popup: PopupMenu = _active_focused_popup(window)
|
||||
if nested_popup != null:
|
||||
return nested_popup
|
||||
var popup := window as PopupMenu
|
||||
if popup != null and popup.get_focused_item() >= 0:
|
||||
return popup
|
||||
return null
|
||||
|
||||
|
||||
func _focus_owner_in_viewport(viewport: Viewport) -> Control:
|
||||
var embedded_windows: Array[Window] = viewport.get_embedded_subwindows()
|
||||
for index: int in range(embedded_windows.size() - 1, -1, -1):
|
||||
var window: Window = embedded_windows[index]
|
||||
if not window.visible:
|
||||
continue
|
||||
var nested_owner: Control = _focus_owner_in_viewport(window)
|
||||
if nested_owner != null:
|
||||
return nested_owner
|
||||
return viewport.gui_get_focus_owner()
|
||||
|
||||
|
||||
func _suppress_native_focus_highlight(control: Control) -> void:
|
||||
_suppressed_control = control
|
||||
_original_style_overrides.clear()
|
||||
_original_color_overrides.clear()
|
||||
for focus_name: StringName in FOCUS_STYLE_REPLACEMENTS:
|
||||
var replacement_name: StringName = FOCUS_STYLE_REPLACEMENTS[focus_name]
|
||||
_original_style_overrides[focus_name] = {
|
||||
"had_override": control.has_theme_stylebox_override(focus_name),
|
||||
"value": control.get_theme_stylebox(focus_name),
|
||||
}
|
||||
var replacement: StyleBox = (
|
||||
StyleBoxEmpty.new()
|
||||
if replacement_name.is_empty()
|
||||
else control.get_theme_stylebox(replacement_name)
|
||||
if control.has_theme_stylebox_override(replacement_name)
|
||||
else StyleBoxEmpty.new()
|
||||
)
|
||||
control.add_theme_stylebox_override(focus_name, replacement)
|
||||
for focus_name: StringName in FOCUS_COLOR_REPLACEMENTS:
|
||||
var replacement_name: StringName = FOCUS_COLOR_REPLACEMENTS[focus_name]
|
||||
if not control.has_theme_color(replacement_name):
|
||||
continue
|
||||
_original_color_overrides[focus_name] = {
|
||||
"had_override": control.has_theme_color_override(focus_name),
|
||||
"value": control.get_theme_color(focus_name),
|
||||
}
|
||||
control.add_theme_color_override(
|
||||
focus_name,
|
||||
control.get_theme_color(replacement_name),
|
||||
)
|
||||
|
||||
|
||||
func _suppress_popup_focus_highlight(popup: PopupMenu) -> void:
|
||||
_original_popup_hover_style = {
|
||||
"had_override": popup.has_theme_stylebox_override(&"hover"),
|
||||
"value": popup.get_theme_stylebox(&"hover"),
|
||||
}
|
||||
_original_popup_hover_color = {
|
||||
"had_override": popup.has_theme_color_override(&"font_hover_color"),
|
||||
"value": popup.get_theme_color(&"font_hover_color"),
|
||||
}
|
||||
popup.add_theme_stylebox_override(&"hover", StyleBoxEmpty.new())
|
||||
popup.add_theme_color_override(
|
||||
&"font_hover_color",
|
||||
popup.get_theme_color(&"font_color"),
|
||||
)
|
||||
|
||||
|
||||
func _restore_native_focus_highlight() -> void:
|
||||
if not is_instance_valid(_suppressed_control):
|
||||
_suppressed_control = null
|
||||
_original_style_overrides.clear()
|
||||
_original_color_overrides.clear()
|
||||
return
|
||||
for focus_name: StringName in _original_style_overrides:
|
||||
var state: Dictionary = _original_style_overrides[focus_name]
|
||||
if bool(state.get("had_override", false)):
|
||||
_suppressed_control.add_theme_stylebox_override(
|
||||
focus_name,
|
||||
state.get("value") as StyleBox,
|
||||
)
|
||||
else:
|
||||
_suppressed_control.remove_theme_stylebox_override(focus_name)
|
||||
for focus_name: StringName in _original_color_overrides:
|
||||
var state: Dictionary = _original_color_overrides[focus_name]
|
||||
if bool(state.get("had_override", false)):
|
||||
_suppressed_control.add_theme_color_override(
|
||||
focus_name,
|
||||
state.get("value") as Color,
|
||||
)
|
||||
else:
|
||||
_suppressed_control.remove_theme_color_override(focus_name)
|
||||
_suppressed_control = null
|
||||
_original_style_overrides.clear()
|
||||
_original_color_overrides.clear()
|
||||
|
||||
|
||||
func _restore_popup_focus_highlight() -> void:
|
||||
if not is_instance_valid(_focused_popup):
|
||||
_original_popup_hover_style.clear()
|
||||
_original_popup_hover_color.clear()
|
||||
return
|
||||
if bool(_original_popup_hover_style.get("had_override", false)):
|
||||
_focused_popup.add_theme_stylebox_override(
|
||||
&"hover",
|
||||
_original_popup_hover_style.get("value") as StyleBox,
|
||||
)
|
||||
else:
|
||||
_focused_popup.remove_theme_stylebox_override(&"hover")
|
||||
if bool(_original_popup_hover_color.get("had_override", false)):
|
||||
_focused_popup.add_theme_color_override(
|
||||
&"font_hover_color",
|
||||
_original_popup_hover_color.get("value") as Color,
|
||||
)
|
||||
else:
|
||||
_focused_popup.remove_theme_color_override(&"font_hover_color")
|
||||
_original_popup_hover_style.clear()
|
||||
_original_popup_hover_color.clear()
|
||||
|
||||
|
||||
func _clear_focus_presentation() -> void:
|
||||
_restore_native_focus_highlight()
|
||||
_restore_popup_focus_highlight()
|
||||
_focused_control = null
|
||||
_focused_popup = null
|
||||
_popup_scroll_offset = 0.0
|
||||
if _focus_layer != null:
|
||||
_focus_layer.custom_viewport = get_viewport()
|
||||
if _focus_arrow != null:
|
||||
_focus_arrow.visible = false
|
||||
|
|
|
|||
BIN
ui/icons/pictograms/arrow_light_up_full.png
Normal file
BIN
ui/icons/pictograms/arrow_light_up_full.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
40
ui/icons/pictograms/arrow_light_up_full.png.import
Normal file
40
ui/icons/pictograms/arrow_light_up_full.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://y07l341wrgkq"
|
||||
path="res://.godot/imported/arrow_light_up_full.png-5033fcbf6f553c3cbfd8b0dd9d5e8b11.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui/icons/pictograms/arrow_light_up_full.png"
|
||||
dest_files=["res://.godot/imported/arrow_light_up_full.png-5033fcbf6f553c3cbfd8b0dd9d5e8b11.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
@ -569,7 +569,6 @@ func _refresh_catalog() -> void:
|
|||
|
||||
func _make_entry(fish: FishDataType, discovered: bool) -> Button:
|
||||
var entry := Button.new()
|
||||
entry.set_meta(&"controller_focus_inversion_disabled", true)
|
||||
entry.custom_minimum_size = CATALOG_ENTRY_SIZE
|
||||
entry.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
entry.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue