Current section

Files

Jump to
flo_ui lib scrollable scroll_bar.ex
Raw

lib/scrollable/scroll_bar.ex

defmodule FloUI.Scrollable.ContainerScrollBar do
@moduledoc false
import Scenic.Primitives, only: [rect: 3]
alias Scenic.Graph
alias Scenic.Primitive
alias FloUI.Scrollable.Direction
alias FloUI.Scrollable.Drag
alias FloUI.Scrollable.Wheel
alias FloUI.Scrollable.PositionCap
alias Scenic.Primitive.Style.Theme
alias Scenic.Math.Vector2
use SnapFramework.Component,
name: :scroll_bar,
template: "lib/scrollable/scroll_bar.eex",
controller: :none,
assigns: [],
opts: []
defcomponent :scroll_bar, :map
@default_drag_settings %{mouse_buttons: [:left, :right, :middle]}
@default_button_radius 3
@default_stroke_size 1
@default_id :scroll_bar
def setup(%{assigns: %{data: data, opts: opts}} = scene) do
request_input(scene, [:cursor_pos])
scene =
assign(scene,
id: opts[:id] || @default_id,
width: data.width,
height: data.height,
direction: data.direction,
content_size: Direction.from_vector_2(data.content_size, data.direction),
frame_size: Direction.from_vector_2({data.width, data.height}, data.direction),
scroll_position: Direction.return(data.scroll_position, data.direction),
scroll_bar_slider_background: :released,
last_scroll_position: Direction.return(data.scroll_position, data.direction),
drag_state: Drag.init(opts[:scroll_drag] || @default_drag_settings),
scroll_buttons:
OptionEx.from_bool(opts[:scroll_buttons], %{
scroll_button_1: :released,
scroll_button_2: :released
}),
pid: self()
)
|> init_position_cap
|> init_size
|> init_scroll_bar_background
|> init_scroll_bar_drag_control
|> init_scroll_bar_buttons
send_parent_event(scene, {:scroll_bar_initialized, scene.assigns})
scene
end
def process_input(
{:cursor_button, {button, :press, _, position}},
:scroll_bar_slider_drag_control,
%{assigns: %{
drag_state: drag_state
}} = scene
) do
Logger.debug("drag control clicked")
drag_state = Drag.handle_mouse_click(drag_state, button, position, local_scroll_position_vector2(scene))
{:noreply, assign(scene, drag_state: drag_state)}
end
def process_input(
{:cursor_button, {button, :release, _, position}},
:scroll_bar_slider_drag_control,
%{assigns: %{drag_state: drag_state}} = scene
) do
drag_state = Drag.handle_mouse_release(drag_state, button, position)
{:noreply, assign(scene, drag_state: drag_state)}
end
def process_input(
{:cursor_pos, position},
_,
%{assigns: %{graph: graph, direction: direction, drag_state: drag_state}} = scene
) do
# Logger.debug(inspect position)
scroll_position =
Direction.from_vector_2(position, direction)
|> Direction.map_horizontal(fn pos -> pos - button_width(scene) / 2 end)
|> Direction.map_vertical(fn pos -> pos - button_height(scene) / 2 end)
scroll_position = local_to_world(scene, scroll_position)
scene =
assign(scene,
drag_state: Drag.handle_mouse_move(drag_state, position),
scroll_bar_slider_background: :released,
last_scroll_position: scroll_position
)
drag_control_position = local_scroll_position_vector2(scene)
graph = Graph.modify(graph, :scroll_bar_slider_drag_control, &Primitive.put_transform(&1, :translate, drag_control_position))
scene =
assign(scene, graph: graph)
|> push_graph(graph)
{:noreply, scene}
end
def process_input(
{:cursor_pos, _},
_,
scene
) do
{:noreply, scene}
end
def process_input(
{:cursor_button, {_button, :press, _, _}},
:scroll_button_1,
%{assigns: %{direction: direction, scroll_buttons: {:some, scroll_buttons}}} = scene
) do
scroll_buttons = %{scroll_buttons | scroll_button_1: :pressed}
send_parent_event(scene, {:scroll_bar_button_pressed, direction, scroll_buttons})
scene =
scene
|> assign(scroll_buttons: {:some, scroll_buttons})
{:noreply, scene}
end
def process_input(
{:cursor_button, {_button, :release, _, _}},
:scroll_button_1,
%{assigns: %{direction: direction, scroll_buttons: {:some, scroll_buttons}}} = scene
) do
scroll_buttons = %{scroll_buttons | scroll_button_1: :released}
send_parent_event(scene, {:scroll_bar_button_released, direction, scroll_buttons})
scene =
scene
|> assign(scroll_buttons: {:some, scroll_buttons})
{:noreply, scene}
end
def process_input(
{:cursor_button, {_button, :press, _, _}},
:scroll_button_2,
%{assigns: %{direction: direction, scroll_buttons: {:some, scroll_buttons}}} = scene
) do
scroll_buttons = %{scroll_buttons | scroll_button_2: :pressed}
send_parent_event(scene, {:scroll_bar_button_pressed, direction, scroll_buttons})
scene =
scene
|> assign(scroll_buttons: {:some, scroll_buttons})
{:noreply, scene}
end
def process_input(
{:cursor_button, {_button, :release, _, _}},
:scroll_button_2,
%{assigns: %{direction: direction, scroll_buttons: {:some, scroll_buttons}}} = scene
) do
scroll_buttons = %{scroll_buttons | scroll_button_2: :released}
send_parent_event(scene, {:scroll_bar_button_released, direction, scroll_buttons})
scene =
scene
|> assign(scroll_buttons: {:some, scroll_buttons})
{:noreply, scene}
end
defp init_scroll_bar_background(%{assigns: %{direction: :vertical, opts: opts, frame_size: frame_size}} = scene) do
scroll_bar_background_width = opts[:scroll_bar_thickness]
scroll_bar_background_height = Direction.unwrap(frame_size)
scroll_bar_background_pos = {0, opts[:scroll_bar_thickness]}
assign(
scene,
scroll_bar_background_width: scroll_bar_background_width,
scroll_bar_background_height: scroll_bar_background_height,
scroll_bar_background_pos: scroll_bar_background_pos
)
end
defp init_scroll_bar_background(%{assigns: %{direction: :horizontal, opts: opts, frame_size: frame_size}} = scene) do
scroll_bar_background_width = Direction.unwrap(frame_size)
scroll_bar_background_height = opts[:scroll_bar_thickness]
scroll_bar_background_pos = {opts[:scroll_bar_thickness], 0}
assign(
scene,
scroll_bar_background_width: scroll_bar_background_width,
scroll_bar_background_height: scroll_bar_background_height,
scroll_bar_background_pos: scroll_bar_background_pos
)
end
defp init_scroll_bar_drag_control(scene) do
scroll_bar_drag_control_width = button_width(scene)
scroll_bar_drag_control_height = button_height(scene)
scroll_bar_drag_control_pos = local_scroll_position_vector2(scene)
Logger.debug(inspect scroll_bar_drag_control_pos)
assign(
scene,
scroll_bar_drag_control_width: scroll_bar_drag_control_width,
scroll_bar_drag_control_height: scroll_bar_drag_control_height,
scroll_bar_drag_control_pos: scroll_bar_drag_control_pos
)
end
defp init_scroll_bar_buttons(%{assigns: %{direction: direction}} = scene) do
size = scroll_button_size(scene)
{button_2_x, button_2_y} =
Direction.return(size, direction)
|> Direction.add(scene.assigns.width)
|> Direction.add(scene.assigns.height)
|> Direction.to_vector_2()
scroll_button_1_width = size
scroll_button_1_height = size
scroll_button_1_pos = {0, -2}
scroll_button_2_width = size
scroll_button_2_height = size
scroll_button_2_pos = {button_2_x, button_2_y + 2}
assign(scene,
scroll_button_1_width: scroll_button_1_width,
scroll_button_1_height: scroll_button_1_height,
scroll_button_1_pos: scroll_button_1_pos,
scroll_button_2_width: scroll_button_2_width,
scroll_button_2_height: scroll_button_2_height,
scroll_button_2_pos: scroll_button_2_pos
)
end
defp init_size(%{assigns: %{scroll_buttons: :none, width: width, height: height}} = scene) do
assign(scene,
width: Direction.as_horizontal(width),
height: Direction.as_vertical(height)
)
end
defp init_size(%{assigns: %{scroll_buttons: {:some, _}, width: width, height: height}} = scene) do
width = Direction.as_horizontal(width)
height = Direction.as_vertical(height)
displacement =
scroll_bar_displacement(
assign(scene, width: width, height: height)
)
button_size_difference = Direction.map(displacement, &(&1 * 2))
assign(scene,
width: Direction.subtract(width, button_size_difference),
height: Direction.subtract(height, button_size_difference),
scroll_bar_displacement: Direction.to_vector_2(displacement)
)
end
defp init_position_cap(%{assigns: %{direction: direction}} = scene) do
max =
Direction.return(0, direction)
|> Direction.add(scene.assigns.width)
|> Direction.add(scene.assigns.height)
|> Direction.map_horizontal(fn width ->
width - button_width(scene) + scroll_button_size(scene)
end)
|> Direction.map_vertical(fn height ->
height - button_height(scene) + scroll_button_size(scene)
end)
|> Direction.to_vector_2()
min =
scroll_bar_displacement(scene)
|> Direction.to_vector_2()
assign(scene, position_cap: PositionCap.init(%{min: min, max: max}))
end
defp scroll_button_size(%{assigns: %{scroll_buttons: :none}}), do: 0
defp scroll_button_size(%{assigns: %{width: width, height: height, direction: direction}}) do
Direction.return(1, direction)
|> Direction.invert()
|> Direction.multiply(width)
|> Direction.multiply(height)
|> Direction.unwrap()
end
defp button_width(%{assigns: %{direction: :horizontal}} = scene) do
Direction.divide(scene.assigns.frame_size, scene.assigns.content_size)
|> Direction.multiply(scene.assigns.width)
|> Direction.unwrap()
end
defp button_width(scene), do: scene.assigns.opts[:scroll_bar_thickness]
defp button_height(%{assigns: %{direction: :vertical}} = scene) do
Direction.divide(scene.assigns.frame_size, scene.assigns.content_size)
|> Direction.multiply(scene.assigns.height)
|> Direction.unwrap()
end
defp button_height(scene), do: scene.assigns.opts[:scroll_bar_thickness]
defp width_factor(%{assigns: %{content_size: {:horizontal, size}, width: {_, width}}}) do
width / size
end
defp width_factor(_), do: 1
defp height_factor(%{assigns: %{content_size: {:vertical, size}, height: {_, height}}}) do
height / size
end
defp height_factor(_), do: 1
# POSITION CALCULATIONS
defp scroll_bar_displacement(%{assigns: %{direction: direction}} = scene) do
scroll_button_size(scene)
|> Direction.return(direction)
end
defp scroll_position_vector2(scene) do
Direction.to_vector_2(scene.assigns.scroll_position)
end
defp local_scroll_position_vector2(scene) do
world_to_local(scene, scroll_position_vector2(scene))
end
defp local_to_world(%{assigns: %{direction: :horizontal}} = scene, {:horizontal, x}) do
{:horizontal, local_to_world(scene, x)}
end
defp local_to_world(%{assigns: %{direction: :vertical}} = assigns, {:vertical, y}) do
{:vertical, local_to_world(assigns, y)}
end
defp local_to_world(_, {:horizontal, _}), do: {:horizontal, 0}
defp local_to_world(_, {:vertical, _}), do: {:vertical, 0}
defp local_to_world(scene, {x, y}) do
{local_to_world(scene, x), local_to_world(scene, y)}
end
defp local_to_world(_, 0), do: 0
defp local_to_world(%{assigns: %{direction: :horizontal}} = scene, x) do
{x, _} = PositionCap.cap(scene.assigns.position_cap, {x, 0})
-(x - scroll_button_size(scene)) / width_factor(scene)
end
defp local_to_world(%{assigns: %{direction: :vertical}} = scene, y) do
{_, y} = PositionCap.cap(scene.assigns.position_cap, {0, y})
-(y - scroll_button_size(scene)) / height_factor(scene)
end
defp world_to_local(%{assigns: %{direction: direction}} = scene, {x, y}) do
position =
Direction.from_vector_2({x, y}, direction)
|> Direction.map(&world_to_local(scene, &1))
|> Direction.to_vector_2()
PositionCap.cap(scene.assigns.position_cap, position)
end
defp world_to_local(%{assigns: %{direction: :horizontal}} = scene, x),
do: -x * width_factor(scene) + scroll_button_size(scene)
defp world_to_local(%{assigns: %{direction: :vertical}} = scene, y),
do: -y * height_factor(scene) + scroll_button_size(scene)
end