Skip to content

Commit

Permalink
Merge pull request #297 from GPrimola/new-component-events
Browse files Browse the repository at this point in the history
New component events
  • Loading branch information
crertel authored May 5, 2024
2 parents 0b6b4b2 + 1b98178 commit aa51267
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 12 deletions.
15 changes: 13 additions & 2 deletions lib/scenic/component/button.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ defmodule Scenic.Component.Button do
If a button press is successful, it sends an event message to the host scene
in the form of:
{:click, id}
`{:click, id}`
This event is only sent after the button is released. There're also, though,
two other events that you can receive:
`{:btn_pressed, id}`
and
`{:btn_released, id}`
These messages can be received and handled in your scene via
`c:Scenic.Scene.handle_event/3`. For example:
Expand Down Expand Up @@ -296,9 +305,10 @@ defmodule Scenic.Component.Button do
def handle_input(
{:cursor_button, {:btn_left, 1, _, _}},
:btn,
%Scene{assigns: %{graph: graph, theme: theme}} = scene
%Scene{assigns: %{id: id, graph: graph, theme: theme}} = scene
) do
:ok = capture_input(scene, :cursor_button)
:ok = send_parent_event(scene, {:btn_pressed, id})

graph = update_color(graph, theme, true)

Expand Down Expand Up @@ -340,6 +350,7 @@ defmodule Scenic.Component.Button do
) do
:ok = release_input(scene)
:ok = send_parent_event(scene, {:click, id})
:ok = send_parent_event(scene, {:btn_released, id})

graph = update_color(graph, theme, false)

Expand Down
16 changes: 15 additions & 1 deletion lib/scenic/component/input/dropdown.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ defmodule Scenic.Component.Input.Dropdown do
`{:value_changed, id, selected_item_id}`
It also send the following events:
`{:dropdown_opened, id}` - sent when the dropdown opens
`{:dropdown_closed, id}` - sent when the dropdown closes
`{:dropdown_item_hover, id, item_id}` - sent when and item is hovered
## Options
Dropdowns honor the following list of options.
Expand Down Expand Up @@ -431,6 +437,7 @@ defmodule Scenic.Component.Input.Dropdown do
%Scene{
assigns: %{
down: true,
id: component_id,
items: items,
graph: graph,
selected_id: selected_id,
Expand All @@ -441,6 +448,8 @@ defmodule Scenic.Component.Input.Dropdown do
# set the appropriate hilighting for each of the items
graph = update_highlighting(graph, items, selected_id, id, theme)

:ok = send_parent_event(scene, {:dropdown_item_hover, component_id, id})

scene =
scene
|> assign(hover_id: nil, graph: graph)
Expand All @@ -453,11 +462,13 @@ defmodule Scenic.Component.Input.Dropdown do
def handle_input(
{:cursor_button, {:btn_left, 1, _, _}},
@button_id,
%Scene{assigns: %{down: false, graph: graph, rotate_caret: rotate_caret}} = scene
%Scene{assigns: %{down: false, graph: graph, id: id, rotate_caret: rotate_caret}} = scene
) do
# capture input
:ok = capture_input(scene, [:cursor_button, :cursor_pos])

:ok = send_parent_event(scene, {:dropdown_opened, id})

# drop the menu
graph =
graph
Expand All @@ -483,6 +494,7 @@ defmodule Scenic.Component.Input.Dropdown do
theme: theme,
items: items,
graph: graph,
id: id,
selected_id: selected_id
}
} = scene
Expand All @@ -496,6 +508,8 @@ defmodule Scenic.Component.Input.Dropdown do

:ok = release_input(scene)

:ok = send_parent_event(scene, {:dropdown_closed, id})

scene =
scene
|> assign(down: false, hover_id: nil, graph: graph)
Expand Down
11 changes: 9 additions & 2 deletions lib/scenic/component/input/text_field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ defmodule Scenic.Component.Input.TextField do
`{:value_changed, id, value}`
It also sends other two events when focus is gained or lost, respectively:
`{:focus, id}`
`{:blur, id}`
## Styles
Text fields honor the following styles
Expand Down Expand Up @@ -254,9 +259,10 @@ defmodule Scenic.Component.Input.TextField do
end

# --------------------------------------------------------
defp capture_focus(%{assigns: %{focused: false, graph: graph, theme: theme}} = scene) do
defp capture_focus(%{assigns: %{focused: false, graph: graph, id: id, theme: theme}} = scene) do
# capture the input
capture_input(scene, @input_capture)
:ok = send_parent_event(scene, {:focus, id})

# start animating the caret
cast_children(scene, :start_caret)
Expand All @@ -274,9 +280,10 @@ defmodule Scenic.Component.Input.TextField do
end

# --------------------------------------------------------
defp release_focus(%{assigns: %{focused: true, graph: graph, theme: theme}} = scene) do
defp release_focus(%{assigns: %{focused: true, graph: graph, id: id, theme: theme}} = scene) do
# release the input
release_input(scene)
:ok = send_parent_event(scene, {:blur, id})

# stop animating the caret
cast_children(scene, :stop_caret)
Expand Down
9 changes: 8 additions & 1 deletion test/scenic/component/button_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,22 @@ defmodule Scenic.Component.ButtonTest do
refute_receive(_, 10)
end

test "Press in and release in sends the event", %{vp: vp} do
test "Press in and release in sends 'click' and 'btn_released' events", %{vp: vp} do
Input.send(vp, @press_in)
Input.send(vp, @release_in)
assert_receive({:fwd_event, {:click, :test_btn}}, 200)
assert_receive({:fwd_event, {:btn_released, :test_btn}}, 200)
end

test "Press in sends a btn_pressed event", %{vp: vp} do
Input.send(vp, @press_in)
assert_receive({:fwd_event, {:btn_pressed, :test_btn}}, 200)
end

test "Press in and release out does not send the event", %{vp: vp} do
Input.send(vp, @press_in)
Input.send(vp, @release_out)
assert_receive({:fwd_event, {:btn_pressed, :test_btn}}, 10)
refute_receive(_, 10)
end

Expand Down
39 changes: 35 additions & 4 deletions test/scenic/component/input/dropdown_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ defmodule Scenic.Component.Input.DropdownTest do
@press_out {:cursor_button, {:btn_left, 1, [], {1000, 1000}}}
@release_out {:cursor_button, {:btn_left, 1, [], {1000, 1000}}}

@hover_a {:cursor_pos, {20, 50}}
@hover_b {:cursor_pos, {20, 80}}

defmodule TestScene do
use Scenic.Scene
import Scenic.Components
Expand Down Expand Up @@ -90,13 +93,18 @@ defmodule Scenic.Component.Input.DropdownTest do
assert_receive({:fwd_event, {:value_changed, :dropdown, 1}}, 100)
end

test "press_in/release_in/press_in does nothing", %{vp: vp, comp_pid: comp_pid} do
test "press_in/release_in/press_in will fire open and close events", %{
vp: vp,
comp_pid: comp_pid
} do
Input.send(vp, @press_in)
force_sync(vp.pid, comp_pid)
Input.send(vp, @release_in)
force_sync(vp.pid, comp_pid)
Input.send(vp, @press_in)
assert_receive({:fwd_event, {:dropdown_opened, :dropdown}}, 100)

Input.send(vp, @press_in)
assert_receive({:fwd_event, {:dropdown_closed, :dropdown}}, 100)
refute_receive(_, 10)
end

Expand All @@ -119,6 +127,8 @@ defmodule Scenic.Component.Input.DropdownTest do
force_sync(vp.pid, comp_pid)
Input.send(vp, @release_in)
force_sync(vp.pid, comp_pid)
assert_receive({:fwd_event, {:dropdown_opened, :dropdown}}, 100)

Input.send(vp, @press_a)
assert_receive({:fwd_event, {:value_changed, :dropdown, 1}}, 100)
end
Expand All @@ -128,15 +138,18 @@ defmodule Scenic.Component.Input.DropdownTest do
force_sync(vp.pid, comp_pid)
Input.send(vp, @release_in)
force_sync(vp.pid, comp_pid)
assert_receive({:fwd_event, {:dropdown_opened, :dropdown}}, 100)

Input.send(vp, @press_b)
assert_receive({:fwd_event, {:value_changed, :dropdown, 2}}, 100)
end

test "Press in and release out does not send the event", %{vp: vp, comp_pid: comp_pid} do
test "Press in and release out send only opened event", %{vp: vp, comp_pid: comp_pid} do
Input.send(vp, @press_in)
force_sync(vp.pid, comp_pid)
Input.send(vp, @release_out)
assert_receive({:fwd_event, {:dropdown_opened, :dropdown}}, 100)

Input.send(vp, @release_out)
refute_receive(_, 10)
end

Expand All @@ -148,6 +161,24 @@ defmodule Scenic.Component.Input.DropdownTest do
refute_receive(_, 10)
end

test "", %{vp: vp, comp_pid: comp_pid} do
Input.send(vp, @press_in)
force_sync(vp.pid, comp_pid)
Input.send(vp, @release_in)
force_sync(vp.pid, comp_pid)
assert_receive({:fwd_event, {:dropdown_opened, :dropdown}}, 100)

Input.send(vp, @hover_a)
force_sync(vp.pid, comp_pid)
assert_receive({:fwd_event, {:dropdown_item_hover, :dropdown, 1}}, 100)

Input.send(vp, @hover_b)
force_sync(vp.pid, comp_pid)
assert_receive({:fwd_event, {:dropdown_item_hover, :dropdown, 2}}, 100)

refute_receive(_, 10)
end

test "implements get/put", %{scene: scene} do
assert Scene.get_child(scene, :dropdown) == [2]
assert Scene.put_child(scene, :dropdown, 1) == :ok
Expand Down
15 changes: 13 additions & 2 deletions test/scenic/component/input/text_field_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,29 @@ defmodule Scenic.Component.Input.TextFieldTest do
:_pong_ = GenServer.call(vp_pid, :_ping_)
end

test "press_in captures and starts editing", %{vp: vp, pid: pid} do
test "press_in captures, starts editing and fire focus event", %{vp: vp, pid: pid} do
assert Input.fetch_captures!(vp) == {:ok, []}
Input.send(vp, @press_in)
force_sync(vp.pid, pid)

assert Input.fetch_captures!(vp) ~> {:ok, sorted_list([:codepoint, :cursor_button, :key])}
assert_receive({:fwd_event, {:focus, :text_field}}, 200)

Input.send(vp, @cp_k)
assert_receive({:fwd_event, {:value_changed, :text_field, "kInitial value"}}, 200)
end

test "press_out releases and ends editing", %{vp: vp, pid: pid} do
test "press_out releases, ends editing and fire blur event", %{vp: vp, pid: pid} do
Input.send(vp, @press_in)
force_sync(vp.pid, pid)

assert Input.fetch_captures!(vp) ~> {:ok, sorted_list([:codepoint, :cursor_button, :key])}
assert_receive({:fwd_event, {:focus, :text_field}}, 200)

Input.send(vp, @press_out)
force_sync(vp.pid, pid)
assert Input.fetch_captures!(vp) == {:ok, []}
assert_receive({:fwd_event, {:blur, :text_field}}, 200)

Input.send(vp, @cp_k)
refute_receive(_, 10)
Expand All @@ -121,6 +125,7 @@ defmodule Scenic.Component.Input.TextFieldTest do
test "pressing in the field moves the cursor to the nearst character gap", %{vp: vp, pid: pid} do
Input.send(vp, @press_in)
force_sync(vp.pid, pid)
assert_receive({:fwd_event, {:focus, :text_field}}, 200)

Input.send(vp, @cp_k)
assert_receive({:fwd_event, {:value_changed, :text_field, "kInitial value"}}, 200)
Expand Down Expand Up @@ -201,6 +206,7 @@ defmodule Scenic.Component.Input.TextFieldTest do
test "backspace does nothing at the start of the string", %{vp: vp, pid: pid} do
Input.send(vp, @press_in)
force_sync(vp.pid, pid)
assert_receive({:fwd_event, {:focus, :text_field}}, 200)

Input.send(vp, @key_backspace)
refute_receive(_, 10)
Expand All @@ -217,6 +223,7 @@ defmodule Scenic.Component.Input.TextFieldTest do
test "delete does nothing at the end of the field", %{vp: vp, pid: pid} do
Input.send(vp, @press_in)
force_sync(vp.pid, pid)
assert_receive({:fwd_event, {:focus, :text_field}}, 200)

Input.send(vp, @key_end)
Input.send(vp, @key_delete)
Expand All @@ -229,6 +236,7 @@ defmodule Scenic.Component.Input.TextFieldTest do

Input.send(vp, {:cursor_button, {:btn_left, 1, [], {20, 60}}})
force_sync(vp.pid, pid)
assert_receive({:fwd_event, {:focus, :number_field}}, 200)

Input.send(vp, {:codepoint, {"a", []}})
refute_receive(_, 10)
Expand All @@ -248,6 +256,7 @@ defmodule Scenic.Component.Input.TextFieldTest do

Input.send(vp, {:cursor_button, {:btn_left, 1, [], {14, 86}}})
force_sync(vp.pid, pid)
assert_receive({:fwd_event, {:focus, :integer_field}}, 200)

Input.send(vp, {:codepoint, {"a", []}})
refute_receive(_, 10)
Expand All @@ -267,6 +276,7 @@ defmodule Scenic.Component.Input.TextFieldTest do

Input.send(vp, {:cursor_button, {:btn_left, 1, [], {14, 121}}})
force_sync(vp.pid, pid)
assert_receive({:fwd_event, {:focus, :abcdefg_field}}, 200)

Input.send(vp, {:codepoint, {"a", []}})
assert_receive({:fwd_event, {:value_changed, :abcdefg_field, "a"}}, 200)
Expand All @@ -284,6 +294,7 @@ defmodule Scenic.Component.Input.TextFieldTest do

Input.send(vp, {:cursor_button, {:btn_left, 1, [], {14, 171}}})
force_sync(vp.pid, pid)
assert_receive({:fwd_event, {:focus, :fn_field}}, 200)

Input.send(vp, {:codepoint, {"a", []}})
refute_receive(_, 10)
Expand Down

0 comments on commit aa51267

Please sign in to comment.