IQ.Pilot Release Commit @ b6534c0
This commit is contained in:
114
iqpilot/system/ui/widgets/confirm_dialog.py
Normal file
114
iqpilot/system/ui/widgets/confirm_dialog.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import pyray as rl
|
||||
from iqpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from iqpilot.system.ui.lib.multilang import tr
|
||||
from iqpilot.system.ui.widgets import DialogResult
|
||||
from iqpilot.system.ui.widgets.button import ButtonStyle, Button
|
||||
from iqpilot.system.ui.widgets.label import Label
|
||||
from iqpilot.system.ui.widgets.html_render import HtmlRenderer, ElementType
|
||||
from iqpilot.system.ui.widgets import Widget
|
||||
from iqpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
|
||||
OUTER_MARGIN = 200
|
||||
RICH_OUTER_MARGIN = 100
|
||||
BUTTON_HEIGHT = 160
|
||||
MARGIN = 50
|
||||
TEXT_PADDING = 10
|
||||
BACKGROUND_COLOR = rl.Color(27, 27, 27, 255)
|
||||
CONFIRM_COLOR = rl.Color(16, 185, 169, 255) # teal (normal confirm)
|
||||
DANGER_COLOR = rl.Color(226, 72, 58, 255) # red (destructive confirm)
|
||||
BUTTON_RADIUS = 44
|
||||
|
||||
# Confirm actions whose button text contains one of these read as destructive (red).
|
||||
DESTRUCTIVE_KEYWORDS = ("uninstall", "reset", "reboot", "power off", "forget", "delete", "remove", "erase", "wipe", "factory")
|
||||
|
||||
|
||||
def _is_destructive(confirm_text) -> bool:
|
||||
text = confirm_text() if callable(confirm_text) else confirm_text
|
||||
return any(k in str(text).lower() for k in DESTRUCTIVE_KEYWORDS)
|
||||
|
||||
|
||||
class ConfirmDialog(Widget):
|
||||
def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False,
|
||||
destructive: bool | None = None):
|
||||
super().__init__()
|
||||
if cancel_text is None:
|
||||
cancel_text = tr("Cancel")
|
||||
self._confirm_color = DANGER_COLOR if (destructive if destructive is not None else _is_destructive(confirm_text)) else CONFIRM_COLOR
|
||||
self._label = Label(text, 70, FontWeight.BOLD, text_color=rl.Color(201, 201, 201, 255))
|
||||
self._html_renderer = HtmlRenderer(text=text, text_size={ElementType.P: 50}, center_text=True)
|
||||
self._cancel_button = Button(cancel_text, self._cancel_button_callback)
|
||||
self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT)
|
||||
self._cancel_button._border_radius = BUTTON_RADIUS
|
||||
self._confirm_button._border_radius = BUTTON_RADIUS
|
||||
self._rich = rich
|
||||
self._dialog_result = DialogResult.NO_ACTION
|
||||
self._cancel_text = cancel_text
|
||||
self._scroller = Scroller([self._html_renderer], line_separator=False, spacing=0)
|
||||
|
||||
def set_text(self, text):
|
||||
if not self._rich:
|
||||
self._label.set_text(text)
|
||||
else:
|
||||
self._html_renderer.parse_html_content(text)
|
||||
|
||||
def reset(self):
|
||||
self._dialog_result = DialogResult.NO_ACTION
|
||||
|
||||
def _cancel_button_callback(self):
|
||||
self._dialog_result = DialogResult.CANCEL
|
||||
|
||||
def _confirm_button_callback(self):
|
||||
self._dialog_result = DialogResult.CONFIRM
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
dialog_x = OUTER_MARGIN if not self._rich else RICH_OUTER_MARGIN
|
||||
dialog_y = OUTER_MARGIN if not self._rich else RICH_OUTER_MARGIN
|
||||
dialog_width = gui_app.width - 2 * dialog_x
|
||||
dialog_height = gui_app.height - 2 * dialog_y
|
||||
dialog_rect = rl.Rectangle(dialog_x, dialog_y, dialog_width, dialog_height)
|
||||
|
||||
bottom = dialog_rect.y + dialog_rect.height
|
||||
button_width = (dialog_rect.width - 3 * MARGIN) // 2
|
||||
cancel_button_x = dialog_rect.x + MARGIN
|
||||
confirm_button_x = dialog_rect.x + dialog_rect.width - button_width - MARGIN
|
||||
button_y = bottom - BUTTON_HEIGHT - MARGIN
|
||||
cancel_button = rl.Rectangle(cancel_button_x, button_y, button_width, BUTTON_HEIGHT)
|
||||
confirm_button = rl.Rectangle(confirm_button_x, button_y, button_width, BUTTON_HEIGHT)
|
||||
|
||||
rl.draw_rectangle_rec(dialog_rect, BACKGROUND_COLOR)
|
||||
|
||||
text_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y + TEXT_PADDING,
|
||||
dialog_rect.width - 2 * MARGIN, dialog_rect.height - BUTTON_HEIGHT - MARGIN - TEXT_PADDING * 2)
|
||||
if not self._rich:
|
||||
self._label.render(text_rect)
|
||||
else:
|
||||
html_rect = rl.Rectangle(text_rect.x, text_rect.y, text_rect.width,
|
||||
self._html_renderer.get_total_height(int(text_rect.width)))
|
||||
self._html_renderer.set_rect(html_rect)
|
||||
self._scroller.render(text_rect)
|
||||
|
||||
if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER):
|
||||
self._dialog_result = DialogResult.CONFIRM
|
||||
elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
|
||||
self._dialog_result = DialogResult.CANCEL
|
||||
|
||||
def _render_confirm(r: rl.Rectangle):
|
||||
roundness = BUTTON_RADIUS / (min(r.width, r.height) / 2)
|
||||
rl.draw_rectangle_rounded(r, roundness, 10, self._confirm_color)
|
||||
self._confirm_button.render(r)
|
||||
|
||||
if self._cancel_text:
|
||||
_render_confirm(confirm_button)
|
||||
self._cancel_button.render(cancel_button)
|
||||
else:
|
||||
full_button_width = dialog_rect.width - 2 * MARGIN
|
||||
full_confirm_button = rl.Rectangle(dialog_rect.x + MARGIN, button_y, full_button_width, BUTTON_HEIGHT)
|
||||
_render_confirm(full_confirm_button)
|
||||
|
||||
return self._dialog_result
|
||||
|
||||
|
||||
def alert_dialog(message: str, button_text: str | None = None):
|
||||
if button_text is None:
|
||||
button_text = tr("OK")
|
||||
return ConfirmDialog(message, button_text, cancel_text="")
|
||||
Reference in New Issue
Block a user