diff --git a/changes/3334.2.feature.rst b/changes/3334.2.feature.rst new file mode 100644 index 0000000000..3a18c19852 --- /dev/null +++ b/changes/3334.2.feature.rst @@ -0,0 +1 @@ +The web backend now supports the ScrollContainer widget. diff --git a/docs/reference/api/containers/scrollcontainer.rst b/docs/reference/api/containers/scrollcontainer.rst index 53020130d2..91ab589054 100644 --- a/docs/reference/api/containers/scrollcontainer.rst +++ b/docs/reference/api/containers/scrollcontainer.rst @@ -36,9 +36,11 @@ overflow controlled by scroll bars. :align: center :width: 450px - .. group-tab:: Web |no| + .. group-tab:: Web - Not supported + .. figure:: /reference/images/scrollcontainer-web.png + :align: center + :width: 450px .. group-tab:: Textual |no| diff --git a/docs/reference/data/widgets_by_platform.csv b/docs/reference/data/widgets_by_platform.csv index 4c7e74cda3..23c385c855 100644 --- a/docs/reference/data/widgets_by_platform.csv +++ b/docs/reference/data/widgets_by_platform.csv @@ -26,7 +26,7 @@ Tree,General Widget,:class:`~toga.Tree`,A widget for displaying a hierarchical t WebView,General Widget,:class:`~toga.WebView`,A panel for displaying HTML,|y|,|y|,|y|,|y|,|y|,, Widget,General Widget,:class:`~toga.Widget`,The base widget,|y|,|y|,|y|,|y|,|y|,|b|,|b| Box,Layout Widget,:class:`~toga.Box`,Container for components,|y|,|y|,|y|,|y|,|y|,|b|,|b| -ScrollContainer,Layout Widget,:class:`~toga.ScrollContainer`,A container that can display a layout larger than the area of the container,|y|,|y|,|y|,|y|,|y|,, +ScrollContainer,Layout Widget,:class:`~toga.ScrollContainer`,A container that can display a layout larger than the area of the container,|y|,|y|,|y|,|y|,|y|,|b|, SplitContainer,Layout Widget,:class:`~toga.SplitContainer`,A container that divides an area into two panels with a movable border,|y|,|y|,|y|,,,, OptionContainer,Layout Widget,:class:`~toga.OptionContainer`,A container that can display multiple labeled tabs of content,|y|,|y|,|y|,|y|,|y|,, Camera,Hardware,:class:`~toga.hardware.camera.Camera`,A sensor that can capture photos and/or video.,|y|,,,|y|,|y|,, diff --git a/docs/reference/images/scrollcontainer-web.png b/docs/reference/images/scrollcontainer-web.png new file mode 100644 index 0000000000..457109a8f3 Binary files /dev/null and b/docs/reference/images/scrollcontainer-web.png differ diff --git a/web/src/toga_web/factory.py b/web/src/toga_web/factory.py index 3aaa3da4fc..f54af1fd91 100644 --- a/web/src/toga_web/factory.py +++ b/web/src/toga_web/factory.py @@ -24,8 +24,8 @@ # from .widgets.optioncontainer import OptionContainer from .widgets.passwordinput import PasswordInput from .widgets.progressbar import ProgressBar +from .widgets.scrollcontainer import ScrollContainer -# from .widgets.scrollcontainer import ScrollContainer # from .widgets.selection import Selection # from .widgets.slider import Slider # from .widgets.splitcontainer import SplitContainer @@ -71,7 +71,7 @@ def not_implemented(feature): "PasswordInput", "ProgressBar", "ActivityIndicator", - # 'ScrollContainer', + "ScrollContainer", # 'Selection', # 'Slider', # 'SplitContainer', diff --git a/web/src/toga_web/widgets/button.py b/web/src/toga_web/widgets/button.py index d1b177c0a5..73632a41f6 100644 --- a/web/src/toga_web/widgets/button.py +++ b/web/src/toga_web/widgets/button.py @@ -1,10 +1,12 @@ +from toga_web.libs import create_proxy + from .base import Widget class Button(Widget): def create(self): self.native = self._create_native_widget("sl-button") - self.native.onclick = self.dom_onclick + self.native.addEventListener("onclick", create_proxy(self.dom_onclick)) def dom_onclick(self, event): self.interface.on_press() diff --git a/web/src/toga_web/widgets/passwordinput.py b/web/src/toga_web/widgets/passwordinput.py index 76b1064e38..fbc96f7571 100644 --- a/web/src/toga_web/widgets/passwordinput.py +++ b/web/src/toga_web/widgets/passwordinput.py @@ -1,5 +1,3 @@ -from toga_web.libs import create_proxy - from .textinput import TextInput @@ -7,10 +5,6 @@ class PasswordInput(TextInput): def create(self): super().create() self.native.setAttribute("type", "password") - self.native.addEventListener("sl-change", create_proxy(self.dom_onchange)) - - def dom_onchange(self, event): - self.interface.on_change(None) def is_valid(self): self.interface.factory.not_implemented("PasswordInput.is_valid()") diff --git a/web/src/toga_web/widgets/scrollcontainer.py b/web/src/toga_web/widgets/scrollcontainer.py new file mode 100644 index 0000000000..d5f362274c --- /dev/null +++ b/web/src/toga_web/widgets/scrollcontainer.py @@ -0,0 +1,79 @@ +from toga_web.libs import create_proxy + +from .base import Widget + + +class ScrollContainer(Widget): + def __init__(self, interface): + super().__init__(interface) + self.native.addEventListener("scroll", create_proxy(self.dom_onscroll)) + + def create(self): + self._horizontal_enabled = True + self._vertical_enabled = True + + self.native = self._create_native_widget( + "div", + classes=["container"], + ) + self._content_div = self._create_native_widget( + "div", + classes=["toga-scroll-content"], + ) + self.native.appendChild(self._content_div) + self._update_overflow() + + def set_content(self, content_impl): + while self._content_div.firstChild: + self._content_div.removeChild(self._content_div.firstChild) + + if content_impl is not None: + self._content_div.appendChild(content_impl.native) + + def _update_overflow(self): + self.native.style.overflowX = "auto" if self._horizontal_enabled else "hidden" + self.native.style.overflowY = "auto" if self._vertical_enabled else "hidden" + + def get_horizontal(self): + return self._horizontal_enabled + + def set_horizontal(self, value): + self._horizontal_enabled = bool(value) + self._update_overflow() + + def get_vertical(self): + return self._vertical_enabled + + def set_vertical(self, value): + self._vertical_enabled = bool(value) + self._update_overflow() + + def get_max_horizontal_position(self): + return max(0, self.native.scrollWidth - self.native.clientWidth) + + def get_max_vertical_position(self): + return max(0, self.native.scrollHeight - self.native.clientHeight) + + def get_horizontal_position(self): + return self.native.scrollLeft + + def get_vertical_position(self): + return self.native.scrollTop + + def set_position(self, horizontal, vertical): + if self._horizontal_enabled: + horizontal = max( + 0, min(int(horizontal), self.get_max_horizontal_position()) + ) + else: + horizontal = self.native.scrollLeft + + if self._vertical_enabled: + vertical = max(0, min(int(vertical), self.get_max_vertical_position())) + else: + vertical = self.native.scrollTop + + self.native.scrollTo(horizontal, vertical) + + def dom_onscroll(self, _evt): + self.interface.on_scroll() diff --git a/web/src/toga_web/widgets/textinput.py b/web/src/toga_web/widgets/textinput.py index fa30b017fc..1760983208 100644 --- a/web/src/toga_web/widgets/textinput.py +++ b/web/src/toga_web/widgets/textinput.py @@ -1,3 +1,5 @@ +from toga_web.libs import create_proxy + from .base import Widget @@ -5,11 +7,15 @@ class TextInput(Widget): def create(self): self._return_listener = None self.native = self._create_native_widget("sl-input") - self.native.onkeyup = self.dom_keyup + self.native.onkeyup = self.dom_onkeyup + self.native.addEventListener("onkeyup", create_proxy(self.dom_onkeyup)) + self.native.addEventListener("sl-change", create_proxy(self.dom_sl_change)) + + def dom_onkeyup(self, event): + self.interface.on_change() - def dom_keyup(self, event): - if event.key == "Enter": - self.interface.on_confirm() + def dom_sl_change(self, event): + self.interface.on_confirm() def set_readonly(self, value): self.native.readOnly = value