mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-30 19:12:07 +08:00
raylib: frame independent scroller (#36227)
* rm that * almost * yess * some work * more * todo * okay viber is good once in a while * temp * chadder can't do this * revert * this was broken anyway * fixes * mouse wheel scroll * some clean up * kinda works * way better * can tap to stop * more clean up * more clean up * revert last mouse * fix * debug only * no print * ahh setup.py fps doesn't affect DEFAULT_FPS ofc * rest * fix text * fix touch valid for network
This commit is contained in:
@@ -71,7 +71,7 @@ class FirehoseLayout(Widget):
|
||||
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height)
|
||||
|
||||
# Handle scrolling and render with clipping
|
||||
scroll_offset = self.scroll_panel.handle_scroll(rect, content_rect)
|
||||
scroll_offset = self.scroll_panel.update(rect, content_rect)
|
||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
|
||||
self._render_content(rect, scroll_offset)
|
||||
rl.end_scissor_mode()
|
||||
@@ -106,9 +106,9 @@ class FirehoseLayout(Widget):
|
||||
|
||||
return height
|
||||
|
||||
def _render_content(self, rect: rl.Rectangle, scroll_offset: rl.Vector2):
|
||||
def _render_content(self, rect: rl.Rectangle, scroll_offset: float):
|
||||
x = int(rect.x + 40)
|
||||
y = int(rect.y + 40 + scroll_offset.y)
|
||||
y = int(rect.y + 40 + scroll_offset)
|
||||
w = int(rect.width - 80)
|
||||
|
||||
# Title
|
||||
|
||||
@@ -109,7 +109,7 @@ class AbstractAlert(Widget, ABC):
|
||||
def _render_scrollable_content(self):
|
||||
content_total_height = self.get_content_height()
|
||||
content_bounds = rl.Rectangle(0, 0, self.scroll_panel_rect.width, content_total_height)
|
||||
scroll_offset = self.scroll_panel.handle_scroll(self.scroll_panel_rect, content_bounds)
|
||||
scroll_offset = self.scroll_panel.update(self.scroll_panel_rect, content_bounds)
|
||||
|
||||
rl.begin_scissor_mode(
|
||||
int(self.scroll_panel_rect.x),
|
||||
@@ -120,7 +120,7 @@ class AbstractAlert(Widget, ABC):
|
||||
|
||||
content_rect_with_scroll = rl.Rectangle(
|
||||
self.scroll_panel_rect.x,
|
||||
self.scroll_panel_rect.y + scroll_offset.y,
|
||||
self.scroll_panel_rect.y + scroll_offset,
|
||||
self.scroll_panel_rect.width,
|
||||
content_total_height,
|
||||
)
|
||||
|
||||
+91
-156
@@ -1,189 +1,124 @@
|
||||
import time
|
||||
import math
|
||||
import pyray as rl
|
||||
from collections import deque
|
||||
from enum import IntEnum
|
||||
from openpilot.system.ui.lib.application import gui_app, MouseEvent, MousePos
|
||||
from openpilot.system.ui.lib.application import gui_app, MouseEvent
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
|
||||
# Scroll constants for smooth scrolling behavior
|
||||
MOUSE_WHEEL_SCROLL_SPEED = 30
|
||||
INERTIA_FRICTION = 0.92 # The rate at which the inertia slows down
|
||||
MIN_VELOCITY = 0.5 # Minimum velocity before stopping the inertia
|
||||
DRAG_THRESHOLD = 12 # Pixels of movement to consider it a drag, not a click
|
||||
BOUNCE_FACTOR = 0.2 # Elastic bounce when scrolling past boundaries
|
||||
BOUNCE_RETURN_SPEED = 0.15 # How quickly it returns from the bounce
|
||||
MAX_BOUNCE_DISTANCE = 150 # Maximum distance for bounce effect
|
||||
FLICK_MULTIPLIER = 1.8 # Multiplier for flick gestures
|
||||
VELOCITY_HISTORY_SIZE = 5 # Track velocity over multiple frames for smoother motion
|
||||
MOUSE_WHEEL_SCROLL_SPEED = 50
|
||||
BOUNCE_RETURN_RATE = 5 # ~0.92 at 60fps
|
||||
MIN_VELOCITY = 2 # px/s, changes from auto scroll to steady state
|
||||
MIN_VELOCITY_FOR_CLICKING = 2 * 60 # px/s, accepts clicks while auto scrolling below this velocity
|
||||
DRAG_THRESHOLD = 12 # pixels of movement to consider it a drag, not a click
|
||||
|
||||
DEBUG = False
|
||||
|
||||
|
||||
class ScrollState(IntEnum):
|
||||
IDLE = 0
|
||||
DRAGGING_CONTENT = 1
|
||||
DRAGGING_SCROLLBAR = 2
|
||||
BOUNCING = 3
|
||||
IDLE = 0 # Not dragging, content may be bouncing or scrolling with inertia
|
||||
DRAGGING_CONTENT = 1 # User is actively dragging the content
|
||||
|
||||
|
||||
class GuiScrollPanel:
|
||||
def __init__(self, show_vertical_scroll_bar: bool = False):
|
||||
def __init__(self):
|
||||
self._scroll_state: ScrollState = ScrollState.IDLE
|
||||
self._last_mouse_y: float = 0.0
|
||||
self._start_mouse_y: float = 0.0 # Track the initial mouse position for drag detection
|
||||
self._offset = rl.Vector2(0, 0)
|
||||
self._view = rl.Rectangle(0, 0, 0, 0)
|
||||
self._show_vertical_scroll_bar: bool = show_vertical_scroll_bar
|
||||
self._velocity_y = 0.0 # Velocity for inertia
|
||||
self._is_dragging: bool = False
|
||||
self._bounce_offset: float = 0.0
|
||||
self._velocity_history: deque[float] = deque(maxlen=VELOCITY_HISTORY_SIZE)
|
||||
self._offset_filter_y = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
|
||||
self._velocity_filter_y = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps)
|
||||
self._last_drag_time: float = 0.0
|
||||
self._content_rect: rl.Rectangle | None = None
|
||||
self._bounds_rect: rl.Rectangle | None = None
|
||||
|
||||
def handle_scroll(self, bounds: rl.Rectangle, content: rl.Rectangle) -> rl.Vector2:
|
||||
# TODO: HACK: this class is driven by mouse events, so we need to ensure we have at least one event to process
|
||||
for mouse_event in gui_app.mouse_events or [MouseEvent(MousePos(0, 0), 0, False, False, False, time.monotonic())]:
|
||||
def update(self, bounds: rl.Rectangle, content: rl.Rectangle) -> float:
|
||||
for mouse_event in gui_app.mouse_events:
|
||||
if mouse_event.slot == 0:
|
||||
self._handle_mouse_event(mouse_event, bounds, content)
|
||||
return self._offset
|
||||
|
||||
def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, content: rl.Rectangle):
|
||||
# Store rectangles for reference
|
||||
self._content_rect = content
|
||||
self._bounds_rect = bounds
|
||||
self._update_state(bounds, content)
|
||||
|
||||
max_scroll_y = max(content.height - bounds.height, 0)
|
||||
return float(self._offset_filter_y.x)
|
||||
|
||||
# Start dragging on mouse press
|
||||
if rl.check_collision_point_rec(mouse_event.pos, bounds) and mouse_event.left_pressed:
|
||||
if self._scroll_state == ScrollState.IDLE or self._scroll_state == ScrollState.BOUNCING:
|
||||
self._scroll_state = ScrollState.DRAGGING_CONTENT
|
||||
if self._show_vertical_scroll_bar:
|
||||
scrollbar_width = rl.gui_get_style(rl.GuiControl.LISTVIEW, rl.GuiListViewProperty.SCROLLBAR_WIDTH)
|
||||
scrollbar_x = bounds.x + bounds.width - scrollbar_width
|
||||
if mouse_event.pos.x >= scrollbar_x:
|
||||
self._scroll_state = ScrollState.DRAGGING_SCROLLBAR
|
||||
|
||||
# TODO: hacky
|
||||
# when clicking while moving, go straight into dragging
|
||||
self._is_dragging = abs(self._velocity_y) > MIN_VELOCITY
|
||||
self._last_mouse_y = mouse_event.pos.y
|
||||
self._start_mouse_y = mouse_event.pos.y
|
||||
self._last_drag_time = mouse_event.t
|
||||
self._velocity_history.clear()
|
||||
self._velocity_y = 0.0
|
||||
self._bounce_offset = 0.0
|
||||
|
||||
# Handle active dragging
|
||||
if self._scroll_state == ScrollState.DRAGGING_CONTENT or self._scroll_state == ScrollState.DRAGGING_SCROLLBAR:
|
||||
if mouse_event.left_down:
|
||||
delta_y = mouse_event.pos.y - self._last_mouse_y
|
||||
|
||||
# Track velocity for inertia
|
||||
time_since_last_drag = mouse_event.t - self._last_drag_time
|
||||
if time_since_last_drag > 0:
|
||||
# TODO: HACK: /2 since we usually get two touch events per frame
|
||||
drag_velocity = delta_y / time_since_last_drag / 60.0 / 2 # TODO: shouldn't be hardcoded
|
||||
self._velocity_history.append(drag_velocity)
|
||||
|
||||
self._last_drag_time = mouse_event.t
|
||||
|
||||
# Detect actual dragging
|
||||
total_drag = abs(mouse_event.pos.y - self._start_mouse_y)
|
||||
if total_drag > DRAG_THRESHOLD:
|
||||
self._is_dragging = True
|
||||
|
||||
if self._scroll_state == ScrollState.DRAGGING_CONTENT:
|
||||
# Add resistance at boundaries
|
||||
if (self._offset.y > 0 and delta_y > 0) or (self._offset.y < -max_scroll_y and delta_y < 0):
|
||||
delta_y *= BOUNCE_FACTOR
|
||||
|
||||
self._offset.y += delta_y
|
||||
elif self._scroll_state == ScrollState.DRAGGING_SCROLLBAR:
|
||||
scroll_ratio = content.height / bounds.height
|
||||
self._offset.y -= delta_y * scroll_ratio
|
||||
|
||||
self._last_mouse_y = mouse_event.pos.y
|
||||
|
||||
elif mouse_event.left_released:
|
||||
# Calculate flick velocity
|
||||
if self._velocity_history:
|
||||
total_weight = 0
|
||||
weighted_velocity = 0.0
|
||||
|
||||
for i, v in enumerate(self._velocity_history):
|
||||
weight = i + 1
|
||||
weighted_velocity += v * weight
|
||||
total_weight += weight
|
||||
|
||||
if total_weight > 0:
|
||||
avg_velocity = weighted_velocity / total_weight
|
||||
self._velocity_y = avg_velocity * FLICK_MULTIPLIER
|
||||
|
||||
# Check bounds
|
||||
if self._offset.y > 0 or self._offset.y < -max_scroll_y:
|
||||
self._scroll_state = ScrollState.BOUNCING
|
||||
else:
|
||||
self._scroll_state = ScrollState.IDLE
|
||||
def _update_state(self, bounds: rl.Rectangle, content: rl.Rectangle):
|
||||
if DEBUG:
|
||||
rl.draw_rectangle_lines(0, 0, abs(int(self._velocity_filter_y.x)), 10, rl.RED)
|
||||
|
||||
# Handle mouse wheel
|
||||
wheel_move = rl.get_mouse_wheel_move()
|
||||
if wheel_move != 0:
|
||||
self._velocity_y = 0.0
|
||||
self._offset_filter_y.x += rl.get_mouse_wheel_move() * MOUSE_WHEEL_SCROLL_SPEED
|
||||
|
||||
if self._show_vertical_scroll_bar:
|
||||
self._offset.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20)
|
||||
rl.gui_scroll_panel(bounds, rl.ffi.NULL, content, self._offset, self._view)
|
||||
else:
|
||||
self._offset.y += wheel_move * MOUSE_WHEEL_SCROLL_SPEED
|
||||
|
||||
if self._offset.y > 0 or self._offset.y < -max_scroll_y:
|
||||
self._scroll_state = ScrollState.BOUNCING
|
||||
|
||||
# Apply inertia (continue scrolling after mouse release)
|
||||
max_scroll_distance = max(0, content.height - bounds.height)
|
||||
if self._scroll_state == ScrollState.IDLE:
|
||||
if abs(self._velocity_y) > MIN_VELOCITY:
|
||||
self._offset.y += self._velocity_y
|
||||
self._velocity_y *= INERTIA_FRICTION
|
||||
above_bounds, below_bounds = self._check_bounds(bounds, content)
|
||||
|
||||
if self._offset.y > 0 or self._offset.y < -max_scroll_y:
|
||||
self._scroll_state = ScrollState.BOUNCING
|
||||
# Decay velocity when idle
|
||||
if abs(self._velocity_filter_y.x) > MIN_VELOCITY:
|
||||
# Faster decay if bouncing back from out of bounds
|
||||
friction = math.exp(-BOUNCE_RETURN_RATE * 1 / gui_app.target_fps)
|
||||
self._velocity_filter_y.x *= friction ** 2 if (above_bounds or below_bounds) else friction
|
||||
else:
|
||||
self._velocity_y = 0.0
|
||||
self._velocity_filter_y.x = 0.0
|
||||
|
||||
# Handle bouncing effect
|
||||
elif self._scroll_state == ScrollState.BOUNCING:
|
||||
target_y = 0.0
|
||||
if self._offset.y < -max_scroll_y:
|
||||
target_y = -max_scroll_y
|
||||
if above_bounds or below_bounds:
|
||||
if above_bounds:
|
||||
self._offset_filter_y.update(0)
|
||||
else:
|
||||
self._offset_filter_y.update(-max_scroll_distance)
|
||||
|
||||
distance = target_y - self._offset.y
|
||||
bounce_step = distance * BOUNCE_RETURN_SPEED
|
||||
self._offset.y += bounce_step
|
||||
self._velocity_y *= INERTIA_FRICTION * 0.8
|
||||
self._offset_filter_y.x += self._velocity_filter_y.x / gui_app.target_fps
|
||||
|
||||
if abs(distance) < 0.5 and abs(self._velocity_y) < MIN_VELOCITY:
|
||||
self._offset.y = target_y
|
||||
self._velocity_y = 0.0
|
||||
elif self._scroll_state == ScrollState.DRAGGING_CONTENT:
|
||||
# Mouse not moving, decay velocity
|
||||
if not len(gui_app.mouse_events):
|
||||
self._velocity_filter_y.update(0.0)
|
||||
|
||||
# Settle to exact bounds
|
||||
if abs(self._offset_filter_y.x) < 1e-2:
|
||||
self._offset_filter_y.x = 0.0
|
||||
elif abs(self._offset_filter_y.x + max_scroll_distance) < 1e-2:
|
||||
self._offset_filter_y.x = -max_scroll_distance
|
||||
|
||||
def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, content: rl.Rectangle):
|
||||
if self._scroll_state == ScrollState.IDLE:
|
||||
if mouse_event.left_pressed:
|
||||
self._start_mouse_y = mouse_event.pos.y
|
||||
# Interrupt scrolling with new drag
|
||||
# TODO: stop scrolling with any tap, need to fix is_touch_valid
|
||||
if abs(self._velocity_filter_y.x) > MIN_VELOCITY_FOR_CLICKING:
|
||||
self._scroll_state = ScrollState.DRAGGING_CONTENT
|
||||
# Start velocity at initial measurement for more immediate response
|
||||
self._velocity_filter_y.initialized = False
|
||||
|
||||
if mouse_event.left_down:
|
||||
if abs(mouse_event.pos.y - self._start_mouse_y) > DRAG_THRESHOLD:
|
||||
self._scroll_state = ScrollState.DRAGGING_CONTENT
|
||||
# Start velocity at initial measurement for more immediate response
|
||||
self._velocity_filter_y.initialized = False
|
||||
|
||||
elif self._scroll_state == ScrollState.DRAGGING_CONTENT:
|
||||
if mouse_event.left_released:
|
||||
self._scroll_state = ScrollState.IDLE
|
||||
else:
|
||||
delta_y = mouse_event.pos.y - self._last_mouse_y
|
||||
above_bounds, below_bounds = self._check_bounds(bounds, content)
|
||||
# Rubber banding effect when out of bands
|
||||
if above_bounds or below_bounds:
|
||||
delta_y /= 3
|
||||
|
||||
# Limit bounce distance
|
||||
if self._scroll_state != ScrollState.DRAGGING_CONTENT:
|
||||
if self._offset.y > MAX_BOUNCE_DISTANCE:
|
||||
self._offset.y = MAX_BOUNCE_DISTANCE
|
||||
elif self._offset.y < -(max_scroll_y + MAX_BOUNCE_DISTANCE):
|
||||
self._offset.y = -(max_scroll_y + MAX_BOUNCE_DISTANCE)
|
||||
self._offset_filter_y.x += delta_y
|
||||
|
||||
# Track velocity for inertia
|
||||
dt = mouse_event.t - self._last_drag_time
|
||||
if dt > 0:
|
||||
drag_velocity = delta_y / dt
|
||||
self._velocity_filter_y.update(drag_velocity)
|
||||
|
||||
# TODO: just store last mouse event!
|
||||
self._last_drag_time = mouse_event.t
|
||||
self._last_mouse_y = mouse_event.pos.y
|
||||
|
||||
def _check_bounds(self, bounds: rl.Rectangle, content: rl.Rectangle) -> tuple[bool, bool]:
|
||||
max_scroll_distance = max(0, content.height - bounds.height)
|
||||
above_bounds = self._offset_filter_y.x > 0
|
||||
below_bounds = self._offset_filter_y.x < -max_scroll_distance
|
||||
return above_bounds, below_bounds
|
||||
|
||||
def is_touch_valid(self):
|
||||
return not self._is_dragging
|
||||
|
||||
def get_normalized_scroll_position(self) -> float:
|
||||
"""Returns the current scroll position as a value from 0.0 to 1.0"""
|
||||
if not self._content_rect or not self._bounds_rect:
|
||||
return 0.0
|
||||
|
||||
max_scroll_y = max(self._content_rect.height - self._bounds_rect.height, 0)
|
||||
if max_scroll_y == 0:
|
||||
return 0.0
|
||||
|
||||
normalized = -self._offset.y / max_scroll_y
|
||||
return max(0.0, min(1.0, normalized))
|
||||
return self._scroll_state == ScrollState.IDLE and abs(self._velocity_filter_y.x) < MIN_VELOCITY_FOR_CLICKING
|
||||
|
||||
+3
-3
@@ -299,20 +299,20 @@ class Setup(Widget):
|
||||
|
||||
def render_custom_software_warning(self, rect: rl.Rectangle):
|
||||
warn_rect = rl.Rectangle(rect.x, rect.y, rect.width, 1500)
|
||||
offset = self._custom_software_warning_body_scroll_panel.handle_scroll(rect, warn_rect)
|
||||
offset = self._custom_software_warning_body_scroll_panel.update(rect, warn_rect)
|
||||
|
||||
button_width = (rect.width - MARGIN * 3) / 2
|
||||
button_y = rect.height - MARGIN - BUTTON_HEIGHT
|
||||
|
||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(button_y - BODY_FONT_SIZE))
|
||||
y_offset = rect.y + offset.y
|
||||
y_offset = rect.y + offset
|
||||
self._custom_software_warning_title_label.render(rl.Rectangle(rect.x + 50, y_offset + 150, rect.width - 265, TITLE_FONT_SIZE))
|
||||
self._custom_software_warning_body_label.render(rl.Rectangle(rect.x + 50, y_offset + 200 , rect.width - 50, BODY_FONT_SIZE * 3))
|
||||
rl.end_scissor_mode()
|
||||
|
||||
self._custom_software_warning_back_button.render(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT))
|
||||
self._custom_software_warning_continue_button.render(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT))
|
||||
if offset.y < (rect.height - warn_rect.height):
|
||||
if offset < (rect.height - warn_rect.height):
|
||||
self._custom_software_warning_continue_button.set_enabled(True)
|
||||
self._custom_software_warning_continue_button.set_text("Continue")
|
||||
|
||||
|
||||
+4
-4
@@ -53,14 +53,14 @@ class TextWindow(Widget):
|
||||
self._textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2)
|
||||
self._wrapped_lines = wrap_text(text, FONT_SIZE, self._textarea_rect.width - 20)
|
||||
self._content_rect = rl.Rectangle(0, 0, self._textarea_rect.width - 20, len(self._wrapped_lines) * LINE_HEIGHT)
|
||||
self._scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True)
|
||||
self._scroll_panel._offset.y = -max(self._content_rect.height - self._textarea_rect.height, 0)
|
||||
self._scroll_panel = GuiScrollPanel()
|
||||
self._scroll_panel._offset_filter_y.x = -max(self._content_rect.height - self._textarea_rect.height, 0)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
scroll = self._scroll_panel.handle_scroll(self._textarea_rect, self._content_rect)
|
||||
scroll = self._scroll_panel.update(self._textarea_rect, self._content_rect)
|
||||
rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height))
|
||||
for i, line in enumerate(self._wrapped_lines):
|
||||
position = rl.Vector2(self._textarea_rect.x + scroll.x, self._textarea_rect.y + scroll.y + i * LINE_HEIGHT)
|
||||
position = rl.Vector2(self._textarea_rect.x, self._textarea_rect.y + scroll + i * LINE_HEIGHT)
|
||||
if position.y + LINE_HEIGHT < self._textarea_rect.y or position.y > self._textarea_rect.y + self._textarea_rect.height:
|
||||
continue
|
||||
rl.draw_text_ex(gui_app.font(), line, position, FONT_SIZE, 0, rl.WHITE)
|
||||
|
||||
@@ -116,10 +116,10 @@ class HtmlRenderer(Widget):
|
||||
|
||||
total_height = self.get_total_height(int(scrollable_rect.width))
|
||||
scroll_content_rect = rl.Rectangle(scrollable_rect.x, scrollable_rect.y, scrollable_rect.width, total_height)
|
||||
scroll_offset = self._scroll_panel.handle_scroll(scrollable_rect, scroll_content_rect)
|
||||
scroll_offset = self._scroll_panel.update(scrollable_rect, scroll_content_rect)
|
||||
|
||||
rl.begin_scissor_mode(int(scrollable_rect.x), int(scrollable_rect.y), int(scrollable_rect.width), int(scrollable_rect.height))
|
||||
self._render_content(scrollable_rect, scroll_offset.y)
|
||||
self._render_content(scrollable_rect, scroll_offset)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
button_width = (rect.width - 3 * 50) // 3
|
||||
|
||||
@@ -339,24 +339,23 @@ class WifiManagerUI(Widget):
|
||||
|
||||
def _draw_network_list(self, rect: rl.Rectangle):
|
||||
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._networks) * ITEM_HEIGHT)
|
||||
offset = self.scroll_panel.handle_scroll(rect, content_rect)
|
||||
clicked = self.scroll_panel.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
|
||||
offset = self.scroll_panel.update(rect, content_rect)
|
||||
|
||||
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
|
||||
for i, network in enumerate(self._networks):
|
||||
y_offset = rect.y + i * ITEM_HEIGHT + offset.y
|
||||
y_offset = rect.y + i * ITEM_HEIGHT + offset
|
||||
item_rect = rl.Rectangle(rect.x, y_offset, rect.width, ITEM_HEIGHT)
|
||||
if not rl.check_collision_recs(item_rect, rect):
|
||||
continue
|
||||
|
||||
self._draw_network_item(item_rect, network, clicked)
|
||||
self._draw_network_item(item_rect, network)
|
||||
if i < len(self._networks) - 1:
|
||||
line_y = int(item_rect.y + item_rect.height - 1)
|
||||
rl.draw_line(int(item_rect.x), int(line_y), int(item_rect.x + item_rect.width), line_y, rl.LIGHTGRAY)
|
||||
|
||||
rl.end_scissor_mode()
|
||||
|
||||
def _draw_network_item(self, rect, network: Network, clicked: bool):
|
||||
def _draw_network_item(self, rect, network: Network):
|
||||
spacing = 50
|
||||
ssid_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT)
|
||||
signal_icon_rect = rl.Rectangle(rect.x + rect.width - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE)
|
||||
@@ -396,18 +395,16 @@ class WifiManagerUI(Widget):
|
||||
self._draw_signal_strength_icon(signal_icon_rect, network)
|
||||
|
||||
def _networks_buttons_callback(self, network):
|
||||
if self.scroll_panel.is_touch_valid():
|
||||
if not network.is_saved and network.security_type != SecurityType.OPEN:
|
||||
self.state = UIState.NEEDS_AUTH
|
||||
self._state_network = network
|
||||
self._password_retry = False
|
||||
elif not network.is_connected:
|
||||
self.connect_to_network(network)
|
||||
if not network.is_saved and network.security_type != SecurityType.OPEN:
|
||||
self.state = UIState.NEEDS_AUTH
|
||||
self._state_network = network
|
||||
self._password_retry = False
|
||||
elif not network.is_connected:
|
||||
self.connect_to_network(network)
|
||||
|
||||
def _forget_networks_buttons_callback(self, network):
|
||||
if self.scroll_panel.is_touch_valid():
|
||||
self.state = UIState.SHOW_FORGET_CONFIRM
|
||||
self._state_network = network
|
||||
self.state = UIState.SHOW_FORGET_CONFIRM
|
||||
self._state_network = network
|
||||
|
||||
def _draw_status_icon(self, rect, network: Network):
|
||||
"""Draw the status icon based on network's connection state"""
|
||||
@@ -449,8 +446,10 @@ class WifiManagerUI(Widget):
|
||||
for n in self._networks:
|
||||
self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=TextAlignment.LEFT,
|
||||
button_style=ButtonStyle.TRANSPARENT_WHITE)
|
||||
self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid())
|
||||
self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI,
|
||||
font_size=45)
|
||||
self._forget_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid())
|
||||
|
||||
def _on_need_auth(self, ssid):
|
||||
network = next((n for n in self._networks if n.ssid == ssid), None)
|
||||
|
||||
@@ -41,12 +41,12 @@ class MultiOptionDialog(Widget):
|
||||
list_content_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, content_h)
|
||||
|
||||
# Scroll and render options
|
||||
offset = self.scroll.handle_scroll(view_rect, list_content_rect)
|
||||
offset = self.scroll.update(view_rect, list_content_rect)
|
||||
valid_click = self.scroll.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
|
||||
|
||||
rl.begin_scissor_mode(int(view_rect.x), int(options_y), int(view_rect.width), int(options_h))
|
||||
for i, option in enumerate(self.options):
|
||||
item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset.y
|
||||
item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset
|
||||
item_rect = rl.Rectangle(view_rect.x, item_y, view_rect.width, ITEM_HEIGHT)
|
||||
|
||||
if rl.check_collision_recs(item_rect, view_rect):
|
||||
|
||||
@@ -52,7 +52,7 @@ class Scroller(Widget):
|
||||
content_height = sum(item.rect.height for item in visible_items) + self._spacing * (len(visible_items))
|
||||
if not self._pad_end:
|
||||
content_height -= self._spacing
|
||||
scroll = self.scroll_panel.handle_scroll(self._rect, rl.Rectangle(0, 0, self._rect.width, content_height))
|
||||
scroll = self.scroll_panel.update(self._rect, rl.Rectangle(0, 0, self._rect.width, content_height))
|
||||
|
||||
rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y),
|
||||
int(self._rect.width), int(self._rect.height))
|
||||
@@ -68,8 +68,7 @@ class Scroller(Widget):
|
||||
cur_height += item.rect.height + self._spacing * (idx != 0)
|
||||
|
||||
# Consider scroll
|
||||
x += scroll.x
|
||||
y += scroll.y
|
||||
y += scroll
|
||||
|
||||
# Update item state
|
||||
item.set_position(x, y)
|
||||
|
||||
Reference in New Issue
Block a user