diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77d4a20475..ffd3d0686b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -237,3 +237,11 @@ jobs: with: name: testbed-failure-app-data-${{ matrix.backend }} path: testbed/app_data/* + # This step is only needed if you're trying to diagnose test failures that + # only occur in CI, and can't be reproduced locally. When it runs, it will + # open an SSH server (URL reported in the logs) so you can ssh into the CI + # machine. + # - uses: actions/checkout@v3 + # - name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + # if: failure() diff --git a/.gitignore b/.gitignore index 822fd730c6..95f5bd0ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ coverage.xml dist build +logs _build distribute-* docs/env diff --git a/android/src/toga_android/images.py b/android/src/toga_android/images.py index 7b736e7e0d..8ed5ededda 100644 --- a/android/src/toga_android/images.py +++ b/android/src/toga_android/images.py @@ -1,7 +1,7 @@ from pathlib import Path from android.graphics import Bitmap, BitmapFactory -from java.io import FileOutputStream +from java.io import ByteArrayOutputStream, FileOutputStream class Image: @@ -23,6 +23,11 @@ def get_width(self): def get_height(self): return self.native.getHeight() + def get_data(self): + stream = ByteArrayOutputStream() + self.native.compress(Bitmap.CompressFormat.PNG, 90, stream) + return bytes(stream.toByteArray()) + def save(self, path): path = Path(path) try: diff --git a/android/src/toga_android/window.py b/android/src/toga_android/window.py index aec16221be..ffd523a7d1 100644 --- a/android/src/toga_android/window.py +++ b/android/src/toga_android/window.py @@ -1,8 +1,13 @@ from decimal import ROUND_UP from android import R +from android.graphics import ( + Bitmap, + Canvas as A_Canvas, +) from android.view import ViewTreeObserver from java import dynamic_proxy +from java.io import ByteArrayOutputStream from .container import Container @@ -97,3 +102,17 @@ def close(self): def set_full_screen(self, is_full_screen): self.interface.factory.not_implemented("Window.set_full_screen()") + + def get_image_data(self): + bitmap = Bitmap.createBitmap( + self.native_content.getWidth(), + self.native_content.getHeight(), + Bitmap.Config.ARGB_8888, + ) + canvas = A_Canvas(bitmap) + # TODO: Need to draw window background as well as the content. + self.native_content.draw(canvas) + + stream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream) + return bytes(stream.toByteArray()) diff --git a/android/tests_backend/probe.py b/android/tests_backend/probe.py index 4b9ded8ba1..d21380ee43 100644 --- a/android/tests_backend/probe.py +++ b/android/tests_backend/probe.py @@ -5,6 +5,7 @@ from android.widget import Button from java import dynamic_proxy from org.beeware.android import MainActivity +from pytest import approx class LayoutListener(dynamic_proxy(ViewTreeObserver.OnGlobalLayoutListener)): @@ -88,7 +89,14 @@ async def redraw(self, message=None, delay=0): print("Redraw timed out") if self.app.run_slow: - delay = min(delay, 1) + delay = max(delay, 1) if delay: print("Waiting for redraw" if message is None else message) await asyncio.sleep(delay) + + def assert_image_size(self, image_size, size): + # Sizes are approximate because of scaling inconsistencies. + assert image_size == ( + approx(size[0] * self.scale_factor, abs=2), + approx(size[1] * self.scale_factor, abs=2), + ) diff --git a/android/tests_backend/widgets/canvas.py b/android/tests_backend/widgets/canvas.py index 84259bb310..55fc16f15d 100644 --- a/android/tests_backend/widgets/canvas.py +++ b/android/tests_backend/widgets/canvas.py @@ -20,10 +20,6 @@ def reference_variant(self, reference): def get_image(self): return Image.open(BytesIO(self.impl.get_image_data())) - def assert_image_size(self, image, width, height): - assert image.width == width * self.scale_factor - assert image.height == height * self.scale_factor - def motion_event(self, action, x, y): time = SystemClock.uptimeMillis() super().motion_event( diff --git a/changes/2063.feature.rst b/changes/2063.feature.rst new file mode 100644 index 0000000000..b776bfdfd8 --- /dev/null +++ b/changes/2063.feature.rst @@ -0,0 +1 @@ +The ability to capture the contents of a window as an image has been added. diff --git a/changes/2103.docs.rst b/changes/2103.docs.rst new file mode 100644 index 0000000000..b57a159b25 --- /dev/null +++ b/changes/2103.docs.rst @@ -0,0 +1 @@ +The widget screenshots were updated to provide examples of widgets on every platform. diff --git a/cocoa/setup.py b/cocoa/setup.py index f9c2736edd..30217c2e40 100644 --- a/cocoa/setup.py +++ b/cocoa/setup.py @@ -7,7 +7,7 @@ version=version, install_requires=[ "fonttools >= 4.42.1, < 5.0.0", - "rubicon-objc >= 0.4.5rc1, < 0.5.0", + "rubicon-objc >= 0.4.7, < 0.5.0", f"toga-core == {version}", ], ) diff --git a/cocoa/src/toga_cocoa/images.py b/cocoa/src/toga_cocoa/images.py index 466b9a0876..012ab55779 100644 --- a/cocoa/src/toga_cocoa/images.py +++ b/cocoa/src/toga_cocoa/images.py @@ -1,3 +1,4 @@ +from ctypes import POINTER, c_char, cast from pathlib import Path from toga_cocoa.libs import ( @@ -8,6 +9,15 @@ ) +def nsdata_to_bytes(data: NSData) -> bytes: + """Convert an NSData into a raw bytes representation""" + # data is an NSData object that has .bytes as a c_void_p, and a .length. Cast to + # POINTER(c_char) to get an addressable array of bytes, and slice that array to + # the known length. We don't use c_char_p because it has handling of NUL + # termination, and POINTER(c_char) allows array subscripting. + return cast(data.bytes, POINTER(c_char))[: data.length] + + class Image: def __init__(self, interface, path=None, data=None): self.interface = interface @@ -40,6 +50,15 @@ def get_width(self): def get_height(self): return self.native.size.height + def get_data(self): + return nsdata_to_bytes( + NSBitmapImageRep.representationOfImageRepsInArray( + self.native.representations, + usingType=NSBitmapImageFileType.PNG, + properties=None, + ) + ) + def save(self, path): path = Path(path) try: diff --git a/cocoa/src/toga_cocoa/widgets/canvas.py b/cocoa/src/toga_cocoa/widgets/canvas.py index 77daa15807..d8c8ca8305 100644 --- a/cocoa/src/toga_cocoa/widgets/canvas.py +++ b/cocoa/src/toga_cocoa/widgets/canvas.py @@ -1,4 +1,3 @@ -from ctypes import POINTER, c_char, cast from math import ceil from rubicon.objc import objc_method, objc_property @@ -7,6 +6,7 @@ from toga.colors import BLACK, TRANSPARENT, color from toga.widgets.canvas import Baseline, FillRule from toga_cocoa.colors import native_color +from toga_cocoa.images import nsdata_to_bytes from toga_cocoa.libs import ( CGFloat, CGPathDrawingMode, @@ -325,15 +325,12 @@ def get_image_data(self): bitmap.setSize(self.native.bounds.size) self.native.cacheDisplayInRect(self.native.bounds, toBitmapImageRep=bitmap) - data = bitmap.representationUsingType( - NSBitmapImageFileType.PNG, - properties=None, + return nsdata_to_bytes( + bitmap.representationUsingType( + NSBitmapImageFileType.PNG, + properties=None, + ) ) - # data is an NSData object that has .bytes as a c_void_p, and a .length. Cast to - # POINTER(c_char) to get an addressable array of bytes, and slice that array to - # the known length. We don't use c_char_p because it has handling of NUL - # termination, and POINTER(c_char) allows array subscripting. - return cast(data.bytes, POINTER(c_char))[: data.length] # Rehint def rehint(self): diff --git a/cocoa/src/toga_cocoa/widgets/webview.py b/cocoa/src/toga_cocoa/widgets/webview.py index 594b6568d4..925164fa59 100644 --- a/cocoa/src/toga_cocoa/widgets/webview.py +++ b/cocoa/src/toga_cocoa/widgets/webview.py @@ -1,5 +1,4 @@ -from rubicon.objc import objc_method, objc_property, py_from_ns -from rubicon.objc.runtime import objc_id +from rubicon.objc import objc_id, objc_method, objc_property, py_from_ns from travertino.size import at_least from toga.widgets.webview import JavaScriptResult diff --git a/cocoa/src/toga_cocoa/window.py b/cocoa/src/toga_cocoa/window.py index ac125ac581..e1d82e1cd6 100644 --- a/cocoa/src/toga_cocoa/window.py +++ b/cocoa/src/toga_cocoa/window.py @@ -1,8 +1,10 @@ from toga.command import Command from toga_cocoa.container import Container +from toga_cocoa.images import nsdata_to_bytes from toga_cocoa.libs import ( SEL, NSBackingStoreBuffered, + NSBitmapImageFileType, NSMakeRect, NSMutableArray, NSPoint, @@ -156,6 +158,10 @@ def __init__(self, interface, title, position, size): self.container = Container(on_refresh=self.content_refreshed) self.native.contentView = self.container.native + # Ensure that the container renders it's background in the same color as the window. + self.native.wantsLayer = True + self.container.native.backgroundColor = self.native.backgroundColor + # By default, no toolbar self._toolbar_items = {} self.native_toolbar = None @@ -293,3 +299,17 @@ def cocoa_windowShouldClose(self): def close(self): self.native.close() + + def get_image_data(self): + bitmap = self.container.native.bitmapImageRepForCachingDisplayInRect( + self.container.native.bounds + ) + bitmap.setSize(self.container.native.bounds.size) + self.container.native.cacheDisplayInRect( + self.container.native.bounds, toBitmapImageRep=bitmap + ) + data = bitmap.representationUsingType( + NSBitmapImageFileType.PNG, + properties=None, + ) + return nsdata_to_bytes(data) diff --git a/cocoa/tests_backend/app.py b/cocoa/tests_backend/app.py index 4ef89ca77a..25f0032787 100644 --- a/cocoa/tests_backend/app.py +++ b/cocoa/tests_backend/app.py @@ -1,7 +1,6 @@ from pathlib import Path -from rubicon.objc import NSPoint, ObjCClass, send_message -from rubicon.objc.runtime import objc_id +from rubicon.objc import NSPoint, ObjCClass, objc_id, send_message from toga_cocoa.keys import cocoa_key, toga_key from toga_cocoa.libs import ( diff --git a/cocoa/tests_backend/probe.py b/cocoa/tests_backend/probe.py index d28bfc049c..df70de74c7 100644 --- a/cocoa/tests_backend/probe.py +++ b/cocoa/tests_backend/probe.py @@ -59,3 +59,8 @@ async def redraw(self, message=None, delay=None): # Running at "normal" speed, we need to release to the event loop # for at least one iteration. `runUntilDate:None` does this. NSRunLoop.currentRunLoop.runUntilDate(None) + + def assert_image_size(self, image_size, size): + # Cocoa reports image sizing in the natural screen coordinates, not the size of + # the backing store. + assert image_size == size diff --git a/cocoa/tests_backend/widgets/canvas.py b/cocoa/tests_backend/widgets/canvas.py index b91d1ebb37..cd2472ef0a 100644 --- a/cocoa/tests_backend/widgets/canvas.py +++ b/cocoa/tests_backend/widgets/canvas.py @@ -40,12 +40,6 @@ def get_image(self): except KeyError: return image - def assert_image_size(self, image, width, height): - # Cocoa reports image sizing in the natural screen coordinates, not the size of - # the backing store. - assert image.width == width - assert image.height == height - async def mouse_press(self, x, y): await self.mouse_event( NSEventType.LeftMouseDown, diff --git a/cocoa/tests_backend/window.py b/cocoa/tests_backend/window.py index 7d6c997cb2..eb7e7ef96d 100644 --- a/cocoa/tests_backend/window.py +++ b/cocoa/tests_backend/window.py @@ -1,8 +1,7 @@ from unittest.mock import Mock -from rubicon.objc import send_message +from rubicon.objc import objc_id, send_message from rubicon.objc.collections import ObjCListInstance -from rubicon.objc.runtime import objc_id from toga_cocoa.libs import ( NSURL, diff --git a/core/src/toga/images.py b/core/src/toga/images.py index b072dd2124..a436a8103c 100644 --- a/core/src/toga/images.py +++ b/core/src/toga/images.py @@ -34,20 +34,23 @@ def __init__( self.path = path else: self.path = Path(path) - self.data = None else: self.path = None - self.data = data self.factory = get_platform_factory() - if self.data is not None: - self._impl = self.factory.Image(interface=self, data=self.data) + if data is not None: + self._impl = self.factory.Image(interface=self, data=data) else: self.path = toga.App.app.paths.app / self.path if not self.path.is_file(): raise FileNotFoundError(f"Image file {self.path} does not exist") self._impl = self.factory.Image(interface=self, path=self.path) + @property + def size(self) -> (int, int): + """The size of the image, as a tuple""" + return (self._impl.get_width(), self._impl.get_height()) + @property def width(self) -> int: """The width of the image, in pixels.""" @@ -58,6 +61,14 @@ def height(self) -> int: """The height of the image, in pixels.""" return self._impl.get_height() + @property + def data(self) -> bytes: + """The raw data for the image, in PNG format. + + :returns: The raw image data in PNG format. + """ + return self._impl.get_data() + def save(self, path: str | Path): """Save image to given path. diff --git a/core/src/toga/window.py b/core/src/toga/window.py index b35431fc9c..227bbb7bb4 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -8,6 +8,7 @@ from toga.command import Command, CommandSet from toga.handlers import AsyncResult, wrapped_handler +from toga.images import Image from toga.platform import get_platform_factory from toga.widgets.base import WidgetRegistry @@ -328,6 +329,12 @@ def close(self) -> None: self._impl.close() self._closed = True + def as_image(self) -> Image: + """Render the current contents of the window as an image. + + :returns: A :class:`toga.Image` containing the window content.""" + return Image(data=self._impl.get_image_data()) + ############################################################ # Dialogs ############################################################ diff --git a/core/tests/test_images.py b/core/tests/test_images.py index 6f5d371763..85f3321349 100644 --- a/core/tests/test_images.py +++ b/core/tests/test_images.py @@ -104,10 +104,18 @@ def test_dimensions(): image = toga.Image(path="resources/toga.png") + assert image.size == (60, 40) assert image.width == 60 assert image.height == 40 +def test_data(): + "The raw data of the image can be retrieved." + image = toga.Image(path="resources/toga.png") + + assert image.data == b"pretend this is PNG image data" + + def test_image_save(): "An image can be saved" save_path = Path("/path/to/save.png") diff --git a/core/tests/test_window.py b/core/tests/test_window.py index 6893887ba6..61a5e1209e 100644 --- a/core/tests/test_window.py +++ b/core/tests/test_window.py @@ -346,6 +346,13 @@ def test_close_rejected_handler(window, app): on_close_handler.assert_called_once_with(window) +def test_as_image(window): + """A window can be captured as an image""" + image = window.as_image() + + assert image.data == b"pretend this is PNG image data" + + def test_info_dialog(window, app): """An info dialog can be shown""" on_result_handler = Mock() diff --git a/demo/toga_demo/app.py b/demo/toga_demo/app.py index 4bf8b52834..507659e0f3 100755 --- a/demo/toga_demo/app.py +++ b/demo/toga_demo/app.py @@ -18,20 +18,24 @@ def startup(self): ], ) - left_tree = toga.Tree( - headings=["Navigate"], - data={ - ("root1",): {}, - ("root2",): { - ("root2.1",): None, - ("root2.2",): { - ("root2.2.1",): None, - ("root2.2.2",): None, - ("root2.2.3",): None, + try: + left_tree = toga.Tree( + headings=["Navigate"], + data={ + ("root1",): {}, + ("root2",): { + ("root2.1",): None, + ("root2.2",): { + ("root2.2.1",): None, + ("root2.2.2",): None, + ("root2.2.3",): None, + }, }, }, - }, - ) + ) + except NotImplementedError: + # For now, Winforms doesn't implement tree. + left_tree = toga.Box() left_container = toga.OptionContainer( content=[ diff --git a/docs/_static/custom.css b/docs/_static/custom.css index c3b309ff5a..02b3970e25 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -23,3 +23,21 @@ span.beta, span.stable { font-size: x-large; line-height: 1; } + +.sphinx-tabs-tab span.beta { + color: lightslategray; + font-variant: italic; + font-size: smaller; + vertical-align: super; +} + +.sphinx-tabs-tab span.no { + color: lightcoral; + font-variant: italic; + font-size: smaller; + vertical-align: super; +} + +.sphinx-tabs-panel p { + text-align: center; +} diff --git a/docs/conf.py b/docs/conf.py index 2969e977d0..e34fe23a6c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -111,8 +111,11 @@ rst_prolog = """ .. role:: stable .. role:: beta +.. role:: no .. |y| replace:: :stable:`●` .. |b| replace:: :beta:`○` +.. |beta| replace:: :beta:`β` +.. |no| replace:: :no:`✖︎` """ intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} diff --git a/docs/images/toga-demo-cocoa.png b/docs/images/toga-demo-cocoa.png new file mode 100644 index 0000000000..684c7c20d3 Binary files /dev/null and b/docs/images/toga-demo-cocoa.png differ diff --git a/docs/images/toga-demo-gtk.png b/docs/images/toga-demo-gtk.png new file mode 100644 index 0000000000..16d0c6cade Binary files /dev/null and b/docs/images/toga-demo-gtk.png differ diff --git a/docs/images/toga-demo-winforms.png b/docs/images/toga-demo-winforms.png new file mode 100644 index 0000000000..663fbd0902 Binary files /dev/null and b/docs/images/toga-demo-winforms.png differ diff --git a/docs/index.rst b/docs/index.rst index 92c7c3f0c7..c6b38bf7af 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,9 +9,25 @@ development. Toga is available on macOS, Windows, Linux (GTK), Android, iOS, and for single-page web apps. -.. figure:: tutorial/screenshots/tutorial-2.png - :align: center - :width: 500 +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /images/toga-demo-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /images/toga-demo-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows + + .. figure:: /images/toga-demo-winforms.png + :align: center + :width: 450px .. rst-class:: row diff --git a/docs/reference/api/containers/optioncontainer.rst b/docs/reference/api/containers/optioncontainer.rst index cdb019484f..5db89609ac 100644 --- a/docs/reference/api/containers/optioncontainer.rst +++ b/docs/reference/api/containers/optioncontainer.rst @@ -3,17 +3,41 @@ OptionContainer A container that can display multiple labeled tabs of content. -.. figure:: /reference/images/OptionContainer.png - :align: center - :width: 300px +.. tabs:: -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(OptionContainer|Component))'} + .. group-tab:: macOS + .. figure:: /reference/images/optioncontainer-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/optioncontainer-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows + + .. figure:: /reference/images/optioncontainer-winforms.png + :align: center + :width: 450px + + .. group-tab:: Android |no| + + Not supported + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/containers/scrollcontainer.rst b/docs/reference/api/containers/scrollcontainer.rst index f43c9dfc3a..cb068354f1 100644 --- a/docs/reference/api/containers/scrollcontainer.rst +++ b/docs/reference/api/containers/scrollcontainer.rst @@ -4,16 +4,45 @@ ScrollContainer A container that can display a layout larger than the area of the container, with overflow controlled by scroll bars. -.. figure:: /reference/images/ScrollContainer.png - :align: center - :width: 300px - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(ScrollContainer|Component))'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/scrollcontainer-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/scrollcontainer-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows + + .. figure:: /reference/images/scrollcontainer-winforms.png + :align: center + :width: 450px + + .. group-tab:: Android + + .. figure:: /reference/images/scrollcontainer-android.png + :align: center + :width: 450px + + .. group-tab:: iOS + + .. figure:: /reference/images/scrollcontainer-iOS.png + :align: center + :width: 450px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/containers/splitcontainer.rst b/docs/reference/api/containers/splitcontainer.rst index 53777f397a..48c0ea8e13 100644 --- a/docs/reference/api/containers/splitcontainer.rst +++ b/docs/reference/api/containers/splitcontainer.rst @@ -3,17 +3,41 @@ SplitContainer A container that divides an area into two panels with a movable border. -.. figure:: /reference/images/SplitContainer.png - :align: center - :width: 300px +.. tabs:: -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(SplitContainer|Component))'} + .. group-tab:: macOS + .. figure:: /reference/images/splitcontainer-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/splitcontainer-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows + + .. figure:: /reference/images/splitcontainer-winforms.png + :align: center + :width: 450px + + .. group-tab:: Android |no| + + Not supported + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/mainwindow.rst b/docs/reference/api/mainwindow.rst index 57e9deb1ef..f7d7ac864a 100644 --- a/docs/reference/api/mainwindow.rst +++ b/docs/reference/api/mainwindow.rst @@ -3,16 +3,37 @@ MainWindow The main window of the application. -.. figure:: /reference/images/MainWindow.png - :align: center - :width: 300px - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(MainWindow|Component))'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/mainwindow-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/mainwindow-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows + + .. figure:: /reference/images/mainwindow-winforms.png + :align: center + :width: 450px + + .. group-tab:: Android + + .. figure:: /reference/images/mainwindow-android.png + :align: center + :width: 450px + + .. group-tab:: iOS + + .. figure:: /reference/images/mainwindow-iOS.png + :align: center + :width: 450px Usage ----- diff --git a/docs/reference/api/widgets/activityindicator.rst b/docs/reference/api/widgets/activityindicator.rst index 168bc99625..0a19d4be23 100644 --- a/docs/reference/api/widgets/activityindicator.rst +++ b/docs/reference/api/widgets/activityindicator.rst @@ -4,15 +4,43 @@ ActivityIndicator A small animated indicator showing activity on a task of indeterminate length, usually rendered as a "spinner" animation. -.. figure:: /reference/images/ActivityIndicator.jpeg - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(ActivityIndicator|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/activityindicator-cocoa.png + :align: center + :width: 100px + + .. group-tab:: Linux + + .. figure:: /reference/images/activityindicator-gtk.png + :align: center + :width: 100px + + .. group-tab:: Windows |no| + + Not supported + + .. group-tab:: Android |no| + + Not supported + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |beta| + + .. .. figure:: /reference/images/activityindicator-web.png + .. :align: center + .. :width: 100px + + Screenshot not available + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/button.rst b/docs/reference/api/widgets/button.rst index 2f6b3118ae..cb588d43ea 100644 --- a/docs/reference/api/widgets/button.rst +++ b/docs/reference/api/widgets/button.rst @@ -3,16 +3,53 @@ Button A button that can be pressed or clicked. -.. figure:: /reference/images/Button.jpeg - :align: center - :width: 300 - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(Button|Component))'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/button-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/button-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/button-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/button-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/button-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |beta| + + .. .. figure:: /reference/images/button-web.png + .. :align: center + .. :width: 300px + + Screenshot not available + + .. group-tab:: Textual |beta| + + .. .. figure:: /reference/images/button-textual.png + .. :align: center + .. :width: 300px + + Screenshot not available Usage ----- diff --git a/docs/reference/api/widgets/canvas.rst b/docs/reference/api/widgets/canvas.rst index 2cc384edd8..8f184520da 100644 --- a/docs/reference/api/widgets/canvas.rst +++ b/docs/reference/api/widgets/canvas.rst @@ -3,16 +3,45 @@ Canvas A drawing area for 2D vector graphics. -.. figure:: /reference/images/Canvas.png - :align: center - :width: 300 - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(Canvas|Component))'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/canvas-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/canvas-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/canvas-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/canvas-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/canvas-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/dateinput.rst b/docs/reference/api/widgets/dateinput.rst index 670a889e1f..2854869094 100644 --- a/docs/reference/api/widgets/dateinput.rst +++ b/docs/reference/api/widgets/dateinput.rst @@ -3,16 +3,39 @@ DateInput A widget to select a calendar date. -.. figure:: /reference/images/DateInput.png - :align: center - :width: 300 - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(DateInput|Component))'} +.. tabs:: + + .. group-tab:: macOS |no| + + Not supported + + .. group-tab:: Linux |no| + + Not supported + + .. group-tab:: Windows + + .. figure:: /reference/images/dateinput-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/dateinput-android.png + :align: center + :width: 300px + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/detailedlist.rst b/docs/reference/api/widgets/detailedlist.rst index 9ba0e20be6..f679552281 100644 --- a/docs/reference/api/widgets/detailedlist.rst +++ b/docs/reference/api/widgets/detailedlist.rst @@ -1,19 +1,46 @@ DetailedList ============ -An ordered list where each item has an icon, a title, and a line of text. Scroll bars -will be provided if necessary. - -.. figure:: /reference/images/DetailedList.png - :width: 300px - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(DetailedList|Component))'} + +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/detailedlist-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/detailedlist-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows |beta| + + .. figure:: /reference/images/detailedlist-winforms.png + :align: center + :width: 450px + + .. group-tab:: Android + + .. figure:: /reference/images/detailedlist-android.png + :align: center + :width: 450px + + .. group-tab:: iOS + + .. figure:: /reference/images/detailedlist-iOS.png + :align: center + :width: 450px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/divider.rst b/docs/reference/api/widgets/divider.rst index c5d34a3db1..d2a8fa9a8b 100644 --- a/docs/reference/api/widgets/divider.rst +++ b/docs/reference/api/widgets/divider.rst @@ -3,15 +3,47 @@ Divider A separator used to visually distinguish two sections of content in a layout. -.. figure:: /reference/images/Divider.jpeg - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(Divider|Component))'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/divider-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/divider-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/divider-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/divider-android.png + :align: center + :width: 300px + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |beta| + + .. .. figure:: /reference/images/divider-web.png + .. :align: center + .. :width: 300px + + Screenshot not available + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/imageview.rst b/docs/reference/api/widgets/imageview.rst index 7ff4cd1bdd..90f9a0da47 100644 --- a/docs/reference/api/widgets/imageview.rst +++ b/docs/reference/api/widgets/imageview.rst @@ -1,15 +1,48 @@ ImageView ========= -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(ImageView|Component)$)'} - A widget that displays an image. +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/imageview.png + :align: center + :width: 150px + + .. group-tab:: Linux + + .. figure:: /reference/images/imageview.png + :align: center + :width: 150px + + .. group-tab:: Windows + + .. figure:: /reference/images/imageview.png + :align: center + :width: 150px + + .. group-tab:: Android + + .. figure:: /reference/images/imageview.png + :align: center + :width: 150px + + .. group-tab:: iOS + + .. figure:: /reference/images/imageview.png + :align: center + :width: 150px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported + Usage ----- diff --git a/docs/reference/api/widgets/label.rst b/docs/reference/api/widgets/label.rst index a0f1bc1591..cfc5119e0c 100644 --- a/docs/reference/api/widgets/label.rst +++ b/docs/reference/api/widgets/label.rst @@ -3,15 +3,53 @@ Label A text label for annotating forms or interfaces. -.. figure:: /reference/images/Label.jpeg - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(Label|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/label-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/label-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/label-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/label-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/label-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |beta| + + .. .. figure:: /reference/images/label-web.png + .. :align: center + .. :width: 300px + + Screenshot not available + + .. group-tab:: Textual |beta| + + .. .. figure:: /reference/images/label-textual.png + .. :align: center + .. :width: 300px + + Screenshot not available Usage ----- diff --git a/docs/reference/api/widgets/multilinetextinput.rst b/docs/reference/api/widgets/multilinetextinput.rst index 8dbf204692..46848bc953 100644 --- a/docs/reference/api/widgets/multilinetextinput.rst +++ b/docs/reference/api/widgets/multilinetextinput.rst @@ -3,9 +3,45 @@ MultilineTextInput A scrollable panel that allows for the display and editing of multiple lines of text. -.. figure:: /reference/images/MultilineTextInput.png - :align: center - :width: 300 +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/multilinetextinput-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/multilinetextinput-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/multilinetextinput-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/multilinetextinput-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/multilinetextinput-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported .. rst-class:: widget-support .. csv-filter:: Availability (:ref:`Key `) diff --git a/docs/reference/api/widgets/numberinput.rst b/docs/reference/api/widgets/numberinput.rst index bb05f5cac2..68bde9474d 100644 --- a/docs/reference/api/widgets/numberinput.rst +++ b/docs/reference/api/widgets/numberinput.rst @@ -3,15 +3,45 @@ NumberInput A text input that is limited to numeric input. -.. figure:: /reference/images/NumberInput.png - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(NumberInput|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/numberinput-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/numberinput-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/numberinput-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/numberinput-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/numberinput-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/passwordinput.rst b/docs/reference/api/widgets/passwordinput.rst index 0f59d097f3..6ecfc810d9 100644 --- a/docs/reference/api/widgets/passwordinput.rst +++ b/docs/reference/api/widgets/passwordinput.rst @@ -5,17 +5,45 @@ A widget to allow the entry of a password. Any value typed by the user will be obscured, allowing the user to see the number of characters they have typed, but not the actual characters. -.. figure:: /reference/images/PasswordInput.png - :align: center - :width: 300 +.. tabs:: -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(PasswordInput|Component)$)'} + .. group-tab:: macOS + .. figure:: /reference/images/passwordinput-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/passwordinput-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/passwordinput-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/passwordinput-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/passwordinput-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/progressbar.rst b/docs/reference/api/widgets/progressbar.rst index 3530362d61..91185a747f 100644 --- a/docs/reference/api/widgets/progressbar.rst +++ b/docs/reference/api/widgets/progressbar.rst @@ -4,15 +4,49 @@ ProgressBar A horizontal bar to visualize task progress. The task being monitored can be of known or indeterminate length. -.. figure:: /reference/images/ProgressBar.jpeg - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(ProgressBar|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/progressbar-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/progressbar-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/progressbar-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/progressbar-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/progressbar-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |beta| + + .. .. figure:: /reference/images/progressbar-web.png + .. :align: center + .. :width: 300px + + Screenshot not available + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/selection.rst b/docs/reference/api/widgets/selection.rst index 6063d59cb8..e543411385 100644 --- a/docs/reference/api/widgets/selection.rst +++ b/docs/reference/api/widgets/selection.rst @@ -3,8 +3,45 @@ Selection A widget to select a single option from a list of alternatives. -.. figure:: /reference/images/Selection.png - :align: center +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/selection-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/selection-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/selection-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/selection-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/selection-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web + + Not supported + + .. group-tab:: Textual + + Not supported .. rst-class:: widget-support .. csv-filter:: Availability (:ref:`Key `) diff --git a/docs/reference/api/widgets/slider.rst b/docs/reference/api/widgets/slider.rst index c698d017b4..a2569d5bdb 100644 --- a/docs/reference/api/widgets/slider.rst +++ b/docs/reference/api/widgets/slider.rst @@ -4,16 +4,45 @@ Slider A widget for selecting a value within a range. The range is shown as a horizontal line, and the selected value is shown as a draggable marker. -.. figure:: /reference/images/Slider.png - :align: center - :width: 300 - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(Slider|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/slider-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/slider-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/slider-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/slider-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/slider-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage diff --git a/docs/reference/api/widgets/switch.rst b/docs/reference/api/widgets/switch.rst index 81d1599242..d4081b9141 100644 --- a/docs/reference/api/widgets/switch.rst +++ b/docs/reference/api/widgets/switch.rst @@ -4,16 +4,49 @@ Switch A clickable button with two stable states: True (on, checked); and False (off, unchecked). The button has a text label. -.. figure:: /reference/images/Switch.jpeg - :align: center - :width: 300 - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(Switch|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/switch-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/switch-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/switch-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/switch-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/switch-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |beta| + + .. .. figure:: /reference/images/switch-web.png + .. :align: center + .. :width: 300px + + Screenshot not available + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/table.rst b/docs/reference/api/widgets/table.rst index a2b67c2615..5257fc0e9c 100644 --- a/docs/reference/api/widgets/table.rst +++ b/docs/reference/api/widgets/table.rst @@ -4,16 +4,43 @@ Table A widget for displaying columns of tabular data. Scroll bars will be provided if necessary. -.. figure:: /reference/images/Table.png - :width: 300px - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(Table|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/table-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/table-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows + + .. figure:: /reference/images/table-winforms.png + :align: center + :width: 450px + + .. group-tab:: Android |beta| + + .. figure:: /reference/images/table-android.png + :align: center + :width: 450px + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/textinput.rst b/docs/reference/api/widgets/textinput.rst index ee1d6f4da4..dfed425639 100644 --- a/docs/reference/api/widgets/textinput.rst +++ b/docs/reference/api/widgets/textinput.rst @@ -3,16 +3,53 @@ TextInput A widget for the display and editing of a single line of text. -.. figure:: /reference/images/TextInput.png - :align: center - :width: 300 - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(TextInput|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/textinput-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/textinput-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/textinput-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/textinput-android.png + :align: center + :width: 300px + + .. group-tab:: iOS + + .. figure:: /reference/images/textinput-iOS.png + :align: center + :width: 300px + + .. group-tab:: Web |beta| + + .. .. figure:: /reference/images/textinput-web.png + .. :align: center + .. :width: 300px + + Screenshot not available + + .. group-tab:: Textual |beta| + + .. .. figure:: /reference/images/textinput-textual.png + .. :align: center + .. :width: 300px + + Screenshot not available Usage ----- diff --git a/docs/reference/api/widgets/timeinput.rst b/docs/reference/api/widgets/timeinput.rst index 381b4e28c1..c6d8527b72 100644 --- a/docs/reference/api/widgets/timeinput.rst +++ b/docs/reference/api/widgets/timeinput.rst @@ -3,16 +3,39 @@ TimeInput A widget to select a clock time. -.. figure:: /reference/images/TimeInput.png - :align: center - :width: 160 - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(TimeInput|Component))'} +.. tabs:: + + .. group-tab:: macOS |no| + + Not supported + + .. group-tab:: Linux |no| + + Not supported + + .. group-tab:: Windows + + .. figure:: /reference/images/timeinput-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android + + .. figure:: /reference/images/timeinput-android.png + :align: center + :width: 300px + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/tree.rst b/docs/reference/api/widgets/tree.rst index 07a1da4318..9bb05f5706 100644 --- a/docs/reference/api/widgets/tree.rst +++ b/docs/reference/api/widgets/tree.rst @@ -4,16 +4,39 @@ Tree A widget for displaying a hierarchical tree of tabular data. Scroll bars will be provided if necessary. -.. figure:: /reference/images/Tree.png - :width: 300px - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(Tree|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/tree-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/tree-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows |no| + + Not supported + + .. group-tab:: Android |no| + + Not supported + + .. group-tab:: iOS |no| + + Not supported + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/widgets/webview.rst b/docs/reference/api/widgets/webview.rst index 9068d912ba..6fbcf2c2a6 100644 --- a/docs/reference/api/widgets/webview.rst +++ b/docs/reference/api/widgets/webview.rst @@ -3,15 +3,45 @@ WebView An embedded web browser. -.. figure:: /reference/images/WebView.jpeg - :align: center - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!^(WebView|Component)$)'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/webview-cocoa.png + :align: center + :width: 450px + + .. group-tab:: Linux + + .. figure:: /reference/images/webview-gtk.png + :align: center + :width: 450px + + .. group-tab:: Windows + + .. figure:: /reference/images/webview-winforms.png + :align: center + :width: 450px + + .. group-tab:: Android + + .. figure:: /reference/images/webview-android.png + :align: center + :width: 450px + + .. group-tab:: iOS + + .. figure:: /reference/images/webview-iOS.png + :align: center + :width: 450px + + .. group-tab:: Web |no| + + Not supported + + .. group-tab:: Textual |no| + + Not supported Usage ----- diff --git a/docs/reference/api/window.rst b/docs/reference/api/window.rst index f622a9cd5f..1c154f0fc9 100644 --- a/docs/reference/api/window.rst +++ b/docs/reference/api/window.rst @@ -3,16 +3,33 @@ Window An operating system-managed container of widgets. -.. figure:: /reference/images/Window.png - :align: center - :width: 300px - -.. rst-class:: widget-support -.. csv-filter:: Availability (:ref:`Key `) - :header-rows: 1 - :file: ../data/widgets_by_platform.csv - :included_cols: 4,5,6,7,8,9,10 - :exclude: {0: '(?!(Window|Component))'} +.. tabs:: + + .. group-tab:: macOS + + .. figure:: /reference/images/window-cocoa.png + :align: center + :width: 300px + + .. group-tab:: Linux + + .. figure:: /reference/images/window-gtk.png + :align: center + :width: 300px + + .. group-tab:: Windows + + .. figure:: /reference/images/window-winforms.png + :align: center + :width: 300px + + .. group-tab:: Android |no| + + Not supported + + .. group-tab:: iOS |no| + + Not supported Usage ----- diff --git a/docs/reference/images/ActivityIndicator.jpeg b/docs/reference/images/ActivityIndicator.jpeg deleted file mode 100644 index 31804075bf..0000000000 Binary files a/docs/reference/images/ActivityIndicator.jpeg and /dev/null differ diff --git a/docs/reference/images/Button.jpeg b/docs/reference/images/Button.jpeg deleted file mode 100644 index b5de5b73bc..0000000000 Binary files a/docs/reference/images/Button.jpeg and /dev/null differ diff --git a/docs/reference/images/Canvas.png b/docs/reference/images/Canvas.png deleted file mode 100644 index 8c2b92a184..0000000000 Binary files a/docs/reference/images/Canvas.png and /dev/null differ diff --git a/docs/reference/images/DateInput.png b/docs/reference/images/DateInput.png deleted file mode 100755 index 7bf3c6ac5d..0000000000 Binary files a/docs/reference/images/DateInput.png and /dev/null differ diff --git a/docs/reference/images/DetailedList.png b/docs/reference/images/DetailedList.png deleted file mode 100644 index 9daa34faca..0000000000 Binary files a/docs/reference/images/DetailedList.png and /dev/null differ diff --git a/docs/reference/images/Divider.jpeg b/docs/reference/images/Divider.jpeg deleted file mode 100644 index 7ed71a3357..0000000000 Binary files a/docs/reference/images/Divider.jpeg and /dev/null differ diff --git a/docs/reference/images/Label.jpeg b/docs/reference/images/Label.jpeg deleted file mode 100644 index c1004c493b..0000000000 Binary files a/docs/reference/images/Label.jpeg and /dev/null differ diff --git a/docs/reference/images/MainWindow.png b/docs/reference/images/MainWindow.png deleted file mode 100644 index b8d72e6fd0..0000000000 Binary files a/docs/reference/images/MainWindow.png and /dev/null differ diff --git a/docs/reference/images/MultilineTextInput.png b/docs/reference/images/MultilineTextInput.png deleted file mode 100644 index 5e0205d378..0000000000 Binary files a/docs/reference/images/MultilineTextInput.png and /dev/null differ diff --git a/docs/reference/images/NumberInput.png b/docs/reference/images/NumberInput.png deleted file mode 100644 index 5929344b4d..0000000000 Binary files a/docs/reference/images/NumberInput.png and /dev/null differ diff --git a/docs/reference/images/OptionContainer.png b/docs/reference/images/OptionContainer.png deleted file mode 100644 index 13c1f6f4f9..0000000000 Binary files a/docs/reference/images/OptionContainer.png and /dev/null differ diff --git a/docs/reference/images/PasswordInput.png b/docs/reference/images/PasswordInput.png deleted file mode 100644 index a8b588f089..0000000000 Binary files a/docs/reference/images/PasswordInput.png and /dev/null differ diff --git a/docs/reference/images/ProgressBar.jpeg b/docs/reference/images/ProgressBar.jpeg deleted file mode 100644 index e5feb8a8e4..0000000000 Binary files a/docs/reference/images/ProgressBar.jpeg and /dev/null differ diff --git a/docs/reference/images/ScrollContainer.png b/docs/reference/images/ScrollContainer.png deleted file mode 100644 index 919121bf21..0000000000 Binary files a/docs/reference/images/ScrollContainer.png and /dev/null differ diff --git a/docs/reference/images/Selection.png b/docs/reference/images/Selection.png deleted file mode 100644 index 7f94ef8243..0000000000 Binary files a/docs/reference/images/Selection.png and /dev/null differ diff --git a/docs/reference/images/Slider.png b/docs/reference/images/Slider.png deleted file mode 100644 index 0bdd7a77ba..0000000000 Binary files a/docs/reference/images/Slider.png and /dev/null differ diff --git a/docs/reference/images/SplitContainer.png b/docs/reference/images/SplitContainer.png deleted file mode 100644 index d45e981406..0000000000 Binary files a/docs/reference/images/SplitContainer.png and /dev/null differ diff --git a/docs/reference/images/Switch.jpeg b/docs/reference/images/Switch.jpeg deleted file mode 100644 index 450717d5ee..0000000000 Binary files a/docs/reference/images/Switch.jpeg and /dev/null differ diff --git a/docs/reference/images/Table.png b/docs/reference/images/Table.png deleted file mode 100644 index 51179c16ac..0000000000 Binary files a/docs/reference/images/Table.png and /dev/null differ diff --git a/docs/reference/images/TextInput.png b/docs/reference/images/TextInput.png deleted file mode 100644 index fc2d45ea27..0000000000 Binary files a/docs/reference/images/TextInput.png and /dev/null differ diff --git a/docs/reference/images/TimeInput.png b/docs/reference/images/TimeInput.png deleted file mode 100755 index 82fe39f14a..0000000000 Binary files a/docs/reference/images/TimeInput.png and /dev/null differ diff --git a/docs/reference/images/Tree.png b/docs/reference/images/Tree.png deleted file mode 100644 index 9dd61810ca..0000000000 Binary files a/docs/reference/images/Tree.png and /dev/null differ diff --git a/docs/reference/images/WebView.jpeg b/docs/reference/images/WebView.jpeg deleted file mode 100644 index 0451d48426..0000000000 Binary files a/docs/reference/images/WebView.jpeg and /dev/null differ diff --git a/docs/reference/images/Window.png b/docs/reference/images/Window.png deleted file mode 100644 index 2efa3ce470..0000000000 Binary files a/docs/reference/images/Window.png and /dev/null differ diff --git a/docs/reference/images/activityindicator-cocoa.png b/docs/reference/images/activityindicator-cocoa.png new file mode 100644 index 0000000000..faf6b69f8f Binary files /dev/null and b/docs/reference/images/activityindicator-cocoa.png differ diff --git a/docs/reference/images/activityindicator-gtk.png b/docs/reference/images/activityindicator-gtk.png new file mode 100644 index 0000000000..fe04f7533d Binary files /dev/null and b/docs/reference/images/activityindicator-gtk.png differ diff --git a/docs/reference/images/button-android.png b/docs/reference/images/button-android.png new file mode 100644 index 0000000000..4df42ee9fb Binary files /dev/null and b/docs/reference/images/button-android.png differ diff --git a/docs/reference/images/button-cocoa.png b/docs/reference/images/button-cocoa.png new file mode 100644 index 0000000000..1d7d7f888b Binary files /dev/null and b/docs/reference/images/button-cocoa.png differ diff --git a/docs/reference/images/button-gtk.png b/docs/reference/images/button-gtk.png new file mode 100644 index 0000000000..d6d4bb556b Binary files /dev/null and b/docs/reference/images/button-gtk.png differ diff --git a/docs/reference/images/button-iOS.png b/docs/reference/images/button-iOS.png new file mode 100644 index 0000000000..5834ddb36d Binary files /dev/null and b/docs/reference/images/button-iOS.png differ diff --git a/docs/reference/images/button-winforms.png b/docs/reference/images/button-winforms.png new file mode 100644 index 0000000000..f19fa751b1 Binary files /dev/null and b/docs/reference/images/button-winforms.png differ diff --git a/docs/reference/images/canvas-android.png b/docs/reference/images/canvas-android.png new file mode 100644 index 0000000000..a67d70e75e Binary files /dev/null and b/docs/reference/images/canvas-android.png differ diff --git a/docs/reference/images/canvas-cocoa.png b/docs/reference/images/canvas-cocoa.png new file mode 100644 index 0000000000..687f076004 Binary files /dev/null and b/docs/reference/images/canvas-cocoa.png differ diff --git a/docs/reference/images/canvas-gtk.png b/docs/reference/images/canvas-gtk.png new file mode 100644 index 0000000000..458308995c Binary files /dev/null and b/docs/reference/images/canvas-gtk.png differ diff --git a/docs/reference/images/canvas-iOS.png b/docs/reference/images/canvas-iOS.png new file mode 100644 index 0000000000..c171913ff3 Binary files /dev/null and b/docs/reference/images/canvas-iOS.png differ diff --git a/docs/reference/images/canvas-winforms.png b/docs/reference/images/canvas-winforms.png new file mode 100644 index 0000000000..9386dae82a Binary files /dev/null and b/docs/reference/images/canvas-winforms.png differ diff --git a/docs/reference/images/dateinput-android.png b/docs/reference/images/dateinput-android.png new file mode 100644 index 0000000000..de857eb42a Binary files /dev/null and b/docs/reference/images/dateinput-android.png differ diff --git a/docs/reference/images/dateinput-winforms.png b/docs/reference/images/dateinput-winforms.png new file mode 100644 index 0000000000..cdc1e4f103 Binary files /dev/null and b/docs/reference/images/dateinput-winforms.png differ diff --git a/docs/reference/images/detailedlist-android.png b/docs/reference/images/detailedlist-android.png new file mode 100644 index 0000000000..038da17583 Binary files /dev/null and b/docs/reference/images/detailedlist-android.png differ diff --git a/docs/reference/images/detailedlist-cocoa.png b/docs/reference/images/detailedlist-cocoa.png new file mode 100644 index 0000000000..0fd3667458 Binary files /dev/null and b/docs/reference/images/detailedlist-cocoa.png differ diff --git a/docs/reference/images/detailedlist-gtk.png b/docs/reference/images/detailedlist-gtk.png new file mode 100644 index 0000000000..5ffdca94e9 Binary files /dev/null and b/docs/reference/images/detailedlist-gtk.png differ diff --git a/docs/reference/images/detailedlist-iOS.png b/docs/reference/images/detailedlist-iOS.png new file mode 100644 index 0000000000..d76789eb1e Binary files /dev/null and b/docs/reference/images/detailedlist-iOS.png differ diff --git a/docs/reference/images/detailedlist-winforms.png b/docs/reference/images/detailedlist-winforms.png new file mode 100644 index 0000000000..526407ce43 Binary files /dev/null and b/docs/reference/images/detailedlist-winforms.png differ diff --git a/docs/reference/images/divider-android.png b/docs/reference/images/divider-android.png new file mode 100644 index 0000000000..3318af431c Binary files /dev/null and b/docs/reference/images/divider-android.png differ diff --git a/docs/reference/images/divider-cocoa.png b/docs/reference/images/divider-cocoa.png new file mode 100644 index 0000000000..69cc675f48 Binary files /dev/null and b/docs/reference/images/divider-cocoa.png differ diff --git a/docs/reference/images/divider-gtk.png b/docs/reference/images/divider-gtk.png new file mode 100644 index 0000000000..a208030a50 Binary files /dev/null and b/docs/reference/images/divider-gtk.png differ diff --git a/docs/reference/images/divider-winforms.png b/docs/reference/images/divider-winforms.png new file mode 100644 index 0000000000..8b9d9332fc Binary files /dev/null and b/docs/reference/images/divider-winforms.png differ diff --git a/docs/reference/images/imageview.png b/docs/reference/images/imageview.png new file mode 100644 index 0000000000..29ab02cb6d Binary files /dev/null and b/docs/reference/images/imageview.png differ diff --git a/docs/reference/images/label-android.png b/docs/reference/images/label-android.png new file mode 100644 index 0000000000..667dc4da6a Binary files /dev/null and b/docs/reference/images/label-android.png differ diff --git a/docs/reference/images/label-cocoa.png b/docs/reference/images/label-cocoa.png new file mode 100644 index 0000000000..f69e897f42 Binary files /dev/null and b/docs/reference/images/label-cocoa.png differ diff --git a/docs/reference/images/label-gtk.png b/docs/reference/images/label-gtk.png new file mode 100644 index 0000000000..aa3a22ec9e Binary files /dev/null and b/docs/reference/images/label-gtk.png differ diff --git a/docs/reference/images/label-iOS.png b/docs/reference/images/label-iOS.png new file mode 100644 index 0000000000..e1c1eece5b Binary files /dev/null and b/docs/reference/images/label-iOS.png differ diff --git a/docs/reference/images/label-winforms.png b/docs/reference/images/label-winforms.png new file mode 100644 index 0000000000..bc092823a0 Binary files /dev/null and b/docs/reference/images/label-winforms.png differ diff --git a/docs/reference/images/mainwindow-android.png b/docs/reference/images/mainwindow-android.png new file mode 100644 index 0000000000..dcf30a08c2 Binary files /dev/null and b/docs/reference/images/mainwindow-android.png differ diff --git a/docs/reference/images/mainwindow-cocoa.png b/docs/reference/images/mainwindow-cocoa.png new file mode 100644 index 0000000000..74bc9e1840 Binary files /dev/null and b/docs/reference/images/mainwindow-cocoa.png differ diff --git a/docs/reference/images/mainwindow-gtk.png b/docs/reference/images/mainwindow-gtk.png new file mode 100644 index 0000000000..f4ed9727a2 Binary files /dev/null and b/docs/reference/images/mainwindow-gtk.png differ diff --git a/docs/reference/images/mainwindow-iOS.png b/docs/reference/images/mainwindow-iOS.png new file mode 100644 index 0000000000..09ef4c73b7 Binary files /dev/null and b/docs/reference/images/mainwindow-iOS.png differ diff --git a/docs/reference/images/mainwindow-winforms.png b/docs/reference/images/mainwindow-winforms.png new file mode 100644 index 0000000000..557037e0aa Binary files /dev/null and b/docs/reference/images/mainwindow-winforms.png differ diff --git a/docs/reference/images/multilinetextinput-android.png b/docs/reference/images/multilinetextinput-android.png new file mode 100644 index 0000000000..496d938a58 Binary files /dev/null and b/docs/reference/images/multilinetextinput-android.png differ diff --git a/docs/reference/images/multilinetextinput-cocoa.png b/docs/reference/images/multilinetextinput-cocoa.png new file mode 100644 index 0000000000..19e6e0bf69 Binary files /dev/null and b/docs/reference/images/multilinetextinput-cocoa.png differ diff --git a/docs/reference/images/multilinetextinput-gtk.png b/docs/reference/images/multilinetextinput-gtk.png new file mode 100644 index 0000000000..255c1113c5 Binary files /dev/null and b/docs/reference/images/multilinetextinput-gtk.png differ diff --git a/docs/reference/images/multilinetextinput-iOS.png b/docs/reference/images/multilinetextinput-iOS.png new file mode 100644 index 0000000000..972a1bbc56 Binary files /dev/null and b/docs/reference/images/multilinetextinput-iOS.png differ diff --git a/docs/reference/images/multilinetextinput-winforms.png b/docs/reference/images/multilinetextinput-winforms.png new file mode 100644 index 0000000000..c462607815 Binary files /dev/null and b/docs/reference/images/multilinetextinput-winforms.png differ diff --git a/docs/reference/images/numberinput-android.png b/docs/reference/images/numberinput-android.png new file mode 100644 index 0000000000..90092374ca Binary files /dev/null and b/docs/reference/images/numberinput-android.png differ diff --git a/docs/reference/images/numberinput-cocoa.png b/docs/reference/images/numberinput-cocoa.png new file mode 100644 index 0000000000..9502675aec Binary files /dev/null and b/docs/reference/images/numberinput-cocoa.png differ diff --git a/docs/reference/images/numberinput-gtk.png b/docs/reference/images/numberinput-gtk.png new file mode 100644 index 0000000000..67313cd1e4 Binary files /dev/null and b/docs/reference/images/numberinput-gtk.png differ diff --git a/docs/reference/images/numberinput-iOS.png b/docs/reference/images/numberinput-iOS.png new file mode 100644 index 0000000000..81ff8a4fe4 Binary files /dev/null and b/docs/reference/images/numberinput-iOS.png differ diff --git a/docs/reference/images/numberinput-winforms.png b/docs/reference/images/numberinput-winforms.png new file mode 100644 index 0000000000..6a297af34a Binary files /dev/null and b/docs/reference/images/numberinput-winforms.png differ diff --git a/docs/reference/images/optioncontainer-cocoa.png b/docs/reference/images/optioncontainer-cocoa.png new file mode 100644 index 0000000000..951d86afc2 Binary files /dev/null and b/docs/reference/images/optioncontainer-cocoa.png differ diff --git a/docs/reference/images/optioncontainer-gtk.png b/docs/reference/images/optioncontainer-gtk.png new file mode 100644 index 0000000000..461e3dba23 Binary files /dev/null and b/docs/reference/images/optioncontainer-gtk.png differ diff --git a/docs/reference/images/optioncontainer-winforms.png b/docs/reference/images/optioncontainer-winforms.png new file mode 100644 index 0000000000..2d3abf2f17 Binary files /dev/null and b/docs/reference/images/optioncontainer-winforms.png differ diff --git a/docs/reference/images/passwordinput-android.png b/docs/reference/images/passwordinput-android.png new file mode 100644 index 0000000000..91e5b13eae Binary files /dev/null and b/docs/reference/images/passwordinput-android.png differ diff --git a/docs/reference/images/passwordinput-cocoa.png b/docs/reference/images/passwordinput-cocoa.png new file mode 100644 index 0000000000..e90efd3262 Binary files /dev/null and b/docs/reference/images/passwordinput-cocoa.png differ diff --git a/docs/reference/images/passwordinput-gtk.png b/docs/reference/images/passwordinput-gtk.png new file mode 100644 index 0000000000..03064bdda9 Binary files /dev/null and b/docs/reference/images/passwordinput-gtk.png differ diff --git a/docs/reference/images/passwordinput-iOS.png b/docs/reference/images/passwordinput-iOS.png new file mode 100644 index 0000000000..7c8f8feeec Binary files /dev/null and b/docs/reference/images/passwordinput-iOS.png differ diff --git a/docs/reference/images/passwordinput-winforms.png b/docs/reference/images/passwordinput-winforms.png new file mode 100644 index 0000000000..82220d5376 Binary files /dev/null and b/docs/reference/images/passwordinput-winforms.png differ diff --git a/docs/reference/images/progressbar-android.png b/docs/reference/images/progressbar-android.png new file mode 100644 index 0000000000..f6470f2498 Binary files /dev/null and b/docs/reference/images/progressbar-android.png differ diff --git a/docs/reference/images/progressbar-cocoa.png b/docs/reference/images/progressbar-cocoa.png new file mode 100644 index 0000000000..b97105bc79 Binary files /dev/null and b/docs/reference/images/progressbar-cocoa.png differ diff --git a/docs/reference/images/progressbar-gtk.png b/docs/reference/images/progressbar-gtk.png new file mode 100644 index 0000000000..43a404746c Binary files /dev/null and b/docs/reference/images/progressbar-gtk.png differ diff --git a/docs/reference/images/progressbar-iOS.png b/docs/reference/images/progressbar-iOS.png new file mode 100644 index 0000000000..40cdc6c96c Binary files /dev/null and b/docs/reference/images/progressbar-iOS.png differ diff --git a/docs/reference/images/progressbar-winforms.png b/docs/reference/images/progressbar-winforms.png new file mode 100644 index 0000000000..98c3f8ad6e Binary files /dev/null and b/docs/reference/images/progressbar-winforms.png differ diff --git a/docs/reference/images/scrollcontainer-android.png b/docs/reference/images/scrollcontainer-android.png new file mode 100644 index 0000000000..115318b02d Binary files /dev/null and b/docs/reference/images/scrollcontainer-android.png differ diff --git a/docs/reference/images/scrollcontainer-cocoa.png b/docs/reference/images/scrollcontainer-cocoa.png new file mode 100644 index 0000000000..c0210b90b9 Binary files /dev/null and b/docs/reference/images/scrollcontainer-cocoa.png differ diff --git a/docs/reference/images/scrollcontainer-gtk.png b/docs/reference/images/scrollcontainer-gtk.png new file mode 100644 index 0000000000..1767fbc81d Binary files /dev/null and b/docs/reference/images/scrollcontainer-gtk.png differ diff --git a/docs/reference/images/scrollcontainer-iOS.png b/docs/reference/images/scrollcontainer-iOS.png new file mode 100644 index 0000000000..4a82d02934 Binary files /dev/null and b/docs/reference/images/scrollcontainer-iOS.png differ diff --git a/docs/reference/images/scrollcontainer-winforms.png b/docs/reference/images/scrollcontainer-winforms.png new file mode 100644 index 0000000000..11d624207b Binary files /dev/null and b/docs/reference/images/scrollcontainer-winforms.png differ diff --git a/docs/reference/images/selection-android.png b/docs/reference/images/selection-android.png new file mode 100644 index 0000000000..05f6697946 Binary files /dev/null and b/docs/reference/images/selection-android.png differ diff --git a/docs/reference/images/selection-cocoa.png b/docs/reference/images/selection-cocoa.png new file mode 100644 index 0000000000..a21abf65ec Binary files /dev/null and b/docs/reference/images/selection-cocoa.png differ diff --git a/docs/reference/images/selection-gtk.png b/docs/reference/images/selection-gtk.png new file mode 100644 index 0000000000..97b7ae6b86 Binary files /dev/null and b/docs/reference/images/selection-gtk.png differ diff --git a/docs/reference/images/selection-iOS.png b/docs/reference/images/selection-iOS.png new file mode 100644 index 0000000000..eaf2df9cdf Binary files /dev/null and b/docs/reference/images/selection-iOS.png differ diff --git a/docs/reference/images/selection-winforms.png b/docs/reference/images/selection-winforms.png new file mode 100644 index 0000000000..b8b1920b30 Binary files /dev/null and b/docs/reference/images/selection-winforms.png differ diff --git a/docs/reference/images/slider-android.png b/docs/reference/images/slider-android.png new file mode 100644 index 0000000000..076a1e48e8 Binary files /dev/null and b/docs/reference/images/slider-android.png differ diff --git a/docs/reference/images/slider-cocoa.png b/docs/reference/images/slider-cocoa.png new file mode 100644 index 0000000000..c488720619 Binary files /dev/null and b/docs/reference/images/slider-cocoa.png differ diff --git a/docs/reference/images/slider-gtk.png b/docs/reference/images/slider-gtk.png new file mode 100644 index 0000000000..1d5cc06e87 Binary files /dev/null and b/docs/reference/images/slider-gtk.png differ diff --git a/docs/reference/images/slider-iOS.png b/docs/reference/images/slider-iOS.png new file mode 100644 index 0000000000..92d9695a8a Binary files /dev/null and b/docs/reference/images/slider-iOS.png differ diff --git a/docs/reference/images/slider-winforms.png b/docs/reference/images/slider-winforms.png new file mode 100644 index 0000000000..74c30ef4d4 Binary files /dev/null and b/docs/reference/images/slider-winforms.png differ diff --git a/docs/reference/images/splitcontainer-cocoa.png b/docs/reference/images/splitcontainer-cocoa.png new file mode 100644 index 0000000000..bfcdd31698 Binary files /dev/null and b/docs/reference/images/splitcontainer-cocoa.png differ diff --git a/docs/reference/images/splitcontainer-gtk.png b/docs/reference/images/splitcontainer-gtk.png new file mode 100644 index 0000000000..860591adf2 Binary files /dev/null and b/docs/reference/images/splitcontainer-gtk.png differ diff --git a/docs/reference/images/splitcontainer-winforms.png b/docs/reference/images/splitcontainer-winforms.png new file mode 100644 index 0000000000..9f49315412 Binary files /dev/null and b/docs/reference/images/splitcontainer-winforms.png differ diff --git a/docs/reference/images/switch-android.png b/docs/reference/images/switch-android.png new file mode 100644 index 0000000000..9c88e2aea5 Binary files /dev/null and b/docs/reference/images/switch-android.png differ diff --git a/docs/reference/images/switch-cocoa.png b/docs/reference/images/switch-cocoa.png new file mode 100644 index 0000000000..c2de515a5f Binary files /dev/null and b/docs/reference/images/switch-cocoa.png differ diff --git a/docs/reference/images/switch-gtk.png b/docs/reference/images/switch-gtk.png new file mode 100644 index 0000000000..ef679a893c Binary files /dev/null and b/docs/reference/images/switch-gtk.png differ diff --git a/docs/reference/images/switch-iOS.png b/docs/reference/images/switch-iOS.png new file mode 100644 index 0000000000..11215ece8f Binary files /dev/null and b/docs/reference/images/switch-iOS.png differ diff --git a/docs/reference/images/switch-winforms.png b/docs/reference/images/switch-winforms.png new file mode 100644 index 0000000000..f719732185 Binary files /dev/null and b/docs/reference/images/switch-winforms.png differ diff --git a/docs/reference/images/table-android.png b/docs/reference/images/table-android.png new file mode 100644 index 0000000000..fa7dfa1146 Binary files /dev/null and b/docs/reference/images/table-android.png differ diff --git a/docs/reference/images/table-cocoa.png b/docs/reference/images/table-cocoa.png new file mode 100644 index 0000000000..4beca508b9 Binary files /dev/null and b/docs/reference/images/table-cocoa.png differ diff --git a/docs/reference/images/table-gtk.png b/docs/reference/images/table-gtk.png new file mode 100644 index 0000000000..9e50d0d8e6 Binary files /dev/null and b/docs/reference/images/table-gtk.png differ diff --git a/docs/reference/images/table-winforms.png b/docs/reference/images/table-winforms.png new file mode 100644 index 0000000000..a95c81f500 Binary files /dev/null and b/docs/reference/images/table-winforms.png differ diff --git a/docs/reference/images/textinput-android.png b/docs/reference/images/textinput-android.png new file mode 100644 index 0000000000..42e1e4d01e Binary files /dev/null and b/docs/reference/images/textinput-android.png differ diff --git a/docs/reference/images/textinput-cocoa.png b/docs/reference/images/textinput-cocoa.png new file mode 100644 index 0000000000..574fb03106 Binary files /dev/null and b/docs/reference/images/textinput-cocoa.png differ diff --git a/docs/reference/images/textinput-gtk.png b/docs/reference/images/textinput-gtk.png new file mode 100644 index 0000000000..b7235d677e Binary files /dev/null and b/docs/reference/images/textinput-gtk.png differ diff --git a/docs/reference/images/textinput-iOS.png b/docs/reference/images/textinput-iOS.png new file mode 100644 index 0000000000..4607f0b726 Binary files /dev/null and b/docs/reference/images/textinput-iOS.png differ diff --git a/docs/reference/images/textinput-winforms.png b/docs/reference/images/textinput-winforms.png new file mode 100644 index 0000000000..4bdf2d0f53 Binary files /dev/null and b/docs/reference/images/textinput-winforms.png differ diff --git a/docs/reference/images/timeinput-android.png b/docs/reference/images/timeinput-android.png new file mode 100644 index 0000000000..c2f4a62f1b Binary files /dev/null and b/docs/reference/images/timeinput-android.png differ diff --git a/docs/reference/images/timeinput-winforms.png b/docs/reference/images/timeinput-winforms.png new file mode 100644 index 0000000000..b1e5ffd725 Binary files /dev/null and b/docs/reference/images/timeinput-winforms.png differ diff --git a/docs/reference/images/tree-cocoa.png b/docs/reference/images/tree-cocoa.png new file mode 100644 index 0000000000..40867d6bff Binary files /dev/null and b/docs/reference/images/tree-cocoa.png differ diff --git a/docs/reference/images/tree-gtk.png b/docs/reference/images/tree-gtk.png new file mode 100644 index 0000000000..ab4ac5ab7c Binary files /dev/null and b/docs/reference/images/tree-gtk.png differ diff --git a/docs/reference/images/webview-android.png b/docs/reference/images/webview-android.png new file mode 100644 index 0000000000..72f1c7a4d8 Binary files /dev/null and b/docs/reference/images/webview-android.png differ diff --git a/docs/reference/images/webview-cocoa.png b/docs/reference/images/webview-cocoa.png new file mode 100644 index 0000000000..9578bad12f Binary files /dev/null and b/docs/reference/images/webview-cocoa.png differ diff --git a/docs/reference/images/webview-gtk.png b/docs/reference/images/webview-gtk.png new file mode 100644 index 0000000000..0f24ca06d8 Binary files /dev/null and b/docs/reference/images/webview-gtk.png differ diff --git a/docs/reference/images/webview-iOS.png b/docs/reference/images/webview-iOS.png new file mode 100644 index 0000000000..454fc4d412 Binary files /dev/null and b/docs/reference/images/webview-iOS.png differ diff --git a/docs/reference/images/webview-winforms.png b/docs/reference/images/webview-winforms.png new file mode 100644 index 0000000000..1338e00bd4 Binary files /dev/null and b/docs/reference/images/webview-winforms.png differ diff --git a/docs/reference/images/window-cocoa.png b/docs/reference/images/window-cocoa.png new file mode 100644 index 0000000000..0435b80e91 Binary files /dev/null and b/docs/reference/images/window-cocoa.png differ diff --git a/docs/reference/images/window-gtk.png b/docs/reference/images/window-gtk.png new file mode 100644 index 0000000000..839f32f9a2 Binary files /dev/null and b/docs/reference/images/window-gtk.png differ diff --git a/docs/reference/images/window-winforms.png b/docs/reference/images/window-winforms.png new file mode 100644 index 0000000000..f40b0a45c0 Binary files /dev/null and b/docs/reference/images/window-winforms.png differ diff --git a/dummy/src/toga_dummy/images.py b/dummy/src/toga_dummy/images.py index 88e918b57e..5949e72c40 100644 --- a/dummy/src/toga_dummy/images.py +++ b/dummy/src/toga_dummy/images.py @@ -16,5 +16,8 @@ def get_width(self): def get_height(self): return 40 + def get_data(self): + return b"pretend this is PNG image data" + def save(self, path): self._action("save", path=path) diff --git a/dummy/src/toga_dummy/window.py b/dummy/src/toga_dummy/window.py index 073d9b75c2..7179239f79 100644 --- a/dummy/src/toga_dummy/window.py +++ b/dummy/src/toga_dummy/window.py @@ -91,6 +91,9 @@ def close(self): self._action("close") self._set_value("visible", False) + def get_image_data(self): + return b"pretend this is PNG image data" + def set_full_screen(self, is_full_screen): self._action("set full screen", full_screen=is_full_screen) diff --git a/examples/screenshot/README.rst b/examples/screenshot/README.rst new file mode 100644 index 0000000000..4e0fc4dde6 --- /dev/null +++ b/examples/screenshot/README.rst @@ -0,0 +1,12 @@ +Screenshot Generator +==================== + +Test app for the Screenshot Generator widget. + +Quickstart +~~~~~~~~~~ + +To run this example: + + $ pip install toga + $ python -m screenshot diff --git a/examples/screenshot/pyproject.toml b/examples/screenshot/pyproject.toml new file mode 100644 index 0000000000..63c7f965ca --- /dev/null +++ b/examples/screenshot/pyproject.toml @@ -0,0 +1,49 @@ +[build-system] +requires = ["briefcase"] + +[tool.briefcase] +project_name = "Screenshot Generator" +bundle = "org.beeware" +version = "0.0.1" +url = "https://beeware.org" +license = "BSD license" +author = "Tiberius Yak" +author_email = "tiberius@beeware.org" + +[tool.briefcase.app.screenshot] +formal_name = "Screenshot Generator" +description = "A testing app" +sources = ["screenshot"] +requires = [ + "../../core", + "pillow", +] + + +[tool.briefcase.app.screenshot.macOS] +requires = [ + "../../cocoa", + "std-nslog>=1.0.0", +] + +[tool.briefcase.app.screenshot.linux] +requires = [ + "../../gtk", +] + +[tool.briefcase.app.screenshot.windows] +requires = [ + "../../winforms", +] + +# Mobile deployments +[tool.briefcase.app.screenshot.iOS] +requires = [ + "../../iOS", + "std-nslog>=1.0.0", +] + +[tool.briefcase.app.screenshot.android] +requires = [ + "../../android", +] diff --git a/examples/screenshot/screenshot/__init__.py b/examples/screenshot/screenshot/__init__.py new file mode 100644 index 0000000000..86826e638c --- /dev/null +++ b/examples/screenshot/screenshot/__init__.py @@ -0,0 +1,9 @@ +# Examples of valid version strings +# __version__ = '1.2.3.dev1' # Development release 1 +# __version__ = '1.2.3a1' # Alpha Release 1 +# __version__ = '1.2.3b1' # Beta Release 1 +# __version__ = '1.2.3rc1' # RC Release 1 +# __version__ = '1.2.3' # Final Release +# __version__ = '1.2.3.post1' # Post Release 1 + +__version__ = "0.0.1" diff --git a/examples/screenshot/screenshot/__main__.py b/examples/screenshot/screenshot/__main__.py new file mode 100644 index 0000000000..1dfc6569a0 --- /dev/null +++ b/examples/screenshot/screenshot/__main__.py @@ -0,0 +1,4 @@ +from screenshot.app import main + +if __name__ == "__main__": + main().main_loop() diff --git a/examples/screenshot/screenshot/app.py b/examples/screenshot/screenshot/app.py new file mode 100644 index 0000000000..b14d592bb7 --- /dev/null +++ b/examples/screenshot/screenshot/app.py @@ -0,0 +1,486 @@ +import asyncio +from datetime import date, time +from io import BytesIO + +from PIL import Image + +import toga +from toga.constants import CENTER, COLUMN +from toga.style import Pack + +from .canvas import draw_tiberius + + +class ScreenshotGeneratorApp(toga.App): + def create_activityindicator(self): + return toga.Box( + children=[ + toga.Box(style=Pack(flex=1)), + toga.ActivityIndicator(running=True, style=Pack(padding=10)), + toga.Box(style=Pack(flex=1)), + ], + style=Pack(width=100), + ) + + def create_button(self): + return toga.Box( + children=[ + toga.Button( + "Launch rocket", + style=Pack(padding=10, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_canvas(self): + canvas = toga.Canvas(style=Pack(padding=10, width=280, height=290)) + draw_tiberius(canvas) + + return toga.Box(children=[canvas], style=Pack(width=280, height=290)) + + def create_dateinput(self): + return toga.Box( + children=[ + toga.Box(style=Pack(flex=1)), + toga.DateInput(value=date(2014, 4, 21), style=Pack(padding=10)), + toga.Box(style=Pack(flex=1)), + ], + style=Pack(width=300), + ) + + def create_detailedlist(self): + brutus_icon = toga.Icon("resources/brutus.png") + user_icon = toga.Icon("resources/user.png") + return toga.DetailedList( + data=[ + { + "icon": brutus_icon, + "title": "Brutus", + "subtitle": "Are you the very model of a modern major general?", + }, + { + "icon": user_icon, + "title": "Major General", + "subtitle": "I have information animal, mineral, and vegetable...", + }, + { + "icon": brutus_icon, + "title": "Brutus", + "subtitle": "Ah - but do you know the kings of England?", + }, + { + "icon": user_icon, + "title": "Major General", + "subtitle": "I can quote the fights historical!", + }, + ], + style=Pack(padding=10, width=self.MAX_WIDTH, height=300), + ) + + def create_divider(self): + return toga.Box( + children=[ + toga.Label( + "I'm on top", style=Pack(flex=1, padding=5, text_align=CENTER) + ), + toga.Divider(direction=toga.Divider.HORIZONTAL, style=Pack(padding=5)), + toga.Label( + "I'm below", style=Pack(flex=1, padding=5, text_align=CENTER) + ), + ], + style=Pack(width=300, direction=COLUMN), + ) + + def create_label(self): + return toga.Box( + children=[ + toga.Label( + "Brutus was here!", + style=Pack(padding=10, text_align=CENTER, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_multilinetextinput(self): + return toga.MultilineTextInput( + value="\n".join( + [ + "I am the very model of a modern Major-General.", + "I've information animal, mineral, and vegetable.", + "I know the kings of England, and I quote the fights historical", + "From Marathon to Waterloo, in order categorical.", + "I'm very well acquainted, too, with matters mathematical,", + "I understand equations, both the simple and quadratical,", + "About binomial theorem I'm teeming with a lot o' news,", + "With many cheerful facts about the square of the hypotenuse.", + "", + "I'm very good at integral and differential calculus;", + "I know the scientific names of beings animalculous:", + "In short, in matters vegetable, animal, and mineral,", + "I am the very model of a modern Major-General.", + ] + ), + style=Pack(padding=10, width=self.MAX_WIDTH, height=200), + ) + + def create_numberinput(self): + return toga.Box( + children=[ + toga.NumberInput( + value=2.71818, + step=0.00001, + style=Pack(padding=10, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_passwordinput(self): + return toga.Box( + children=[ + toga.PasswordInput( + value="secret", + style=Pack(padding=10, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_progressbar(self): + return toga.Box( + children=[ + toga.ProgressBar( + value=42, + max=100, + style=Pack(padding=10, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_selection(self): + return toga.Box( + children=[ + toga.Selection( + items=["Titanium", "Yttrium", "Yterbium"], + style=Pack(padding=10, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_slider(self): + return toga.Box( + children=[ + toga.Slider( + value=42, + max=100, + style=Pack(padding=10, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_switch(self): + return toga.Box( + children=[ + toga.Box(style=Pack(flex=1)), + toga.Switch( + "Turbo", + value=True, + style=Pack(padding=10), + ), + toga.Box(style=Pack(flex=1)), + ], + style=Pack(width=150), + ) + + def create_table(self): + return toga.Table( + headings=["Name", "Age", "Planet"], + data=[ + ("Arthur Dent", 42, "Earth"), + ("Ford Prefect", 37, "Betelgeuse Five"), + ("Tricia McMillan", 38, "Earth"), + ("Slartibartfast", 1005, "Magrathea"), + ], + style=Pack(padding=10, width=self.MAX_WIDTH, height=200), + ) + + def create_textinput(self): + return toga.Box( + children=[ + toga.TextInput( + value="Brutus was here!", + style=Pack(padding=10, flex=1), + ) + ], + style=Pack(width=300), + ) + + def create_timeinput(self): + return toga.Box( + children=[ + toga.Box(style=Pack(flex=1)), + toga.TimeInput(value=time(9, 7, 37), style=Pack(padding=10)), + toga.Box(style=Pack(flex=1)), + ], + style=Pack(width=300), + ) + + def create_tree(self): + tree = toga.Tree( + headings=["Name", "Age", "Status"], + data={ + "Earth": { + ("Arthur Dent", 42, "Anxious"): None, + ("Tricia McMillan", 38, "Overqualified"): None, + }, + "Betelgeuse Five": { + ("Ford Prefect", 37, "Hoopy"): None, + }, + "Magrathea": { + ("Slartibartfast", 1005, "Annoyed"): None, + }, + }, + style=Pack(padding=10, width=self.MAX_WIDTH, height=200), + ) + tree.expand() + return tree + + def create_webview(self): + return toga.WebView( + url="https://beeware.org", + style=Pack(padding=10, width=self.MAX_WIDTH, height=300), + ) + + def create_optioncontainer(self): + container = toga.OptionContainer( + content=[ + ( + "Blue", + toga.Box(style=Pack(background_color="cornflowerblue")), + ), + ("Green", toga.Box()), + ("Red", toga.Box()), + ], + style=Pack(padding=10, width=self.MAX_WIDTH, height=300), + ) + + return container + + def create_scrollcontainer(self): + container = toga.ScrollContainer( + content=toga.Box( + children=[ + toga.Box( + style=Pack( + background_color="cornflowerblue", width=900, height=600 + ) + ), + ], + style=Pack(direction=COLUMN), + ), + style=Pack(padding=10, width=self.MAX_WIDTH, height=300), + ) + + return container + + def create_splitcontainer(self): + container = toga.SplitContainer( + content=[ + toga.Box(style=Pack(background_color="goldenrod")), + toga.Box(style=Pack(background_color="cornflowerblue")), + ], + style=Pack(padding=10, width=self.MAX_WIDTH, height=300), + ) + + return container + + def create_window(self): + if toga.platform.current_platform in {"iOS", "android"}: + return None + + return toga.Window(title="Toga", position=(800, 200), size=(300, 250)) + + def create_main_window(self): + # No widget to create + return True + + async def manual_screenshot(self, content=None): + loop = asyncio.get_event_loop() + future = loop.create_future() + + def proceed(button, **kwargs): + future.set_result(True) + + proceed_button = toga.Button( + "Done", + on_press=proceed, + style=Pack(padding=10), + ) + + if content: + self.main_window.content = toga.Box( + children=[ + content, + toga.Box(style=Pack(flex=1)), + proceed_button, + ], + style=Pack(direction=COLUMN), + ) + else: + self.main_window.content = toga.Box() + await future + + async def sequence(self, app, **kwargs): + print(f"Saving screenshots to {self.app.paths.data}") + self.app.paths.data.mkdir(parents=True, exist_ok=True) + for content_type in [ + "activityindicator", + "button", + "canvas", + "dateinput", + "detailedlist", + "divider", + "label", + "multilinetextinput", + "numberinput", + "passwordinput", + "progressbar", + "selection", + "slider", + "switch", + "table", + "textinput", + "timeinput", + "tree", + "webview", + "optioncontainer", + "scrollcontainer", + "splitcontainer", + "window", + "main_window", + ]: + try: + content = getattr(self.app, f"create_{content_type}")() + if content: + if content_type == "main_window": + # image = self.main_window.screen.as_image() + # cropped = image.crop(... crop to window size ...) + # + # TODO: Crop the desktop image, rather than use a manual screenshot + await self.main_window.info_dialog( + "Manual intervention", + "Screenshot the main window, and then quit the app.", + ) + self.main_window.toolbar.add(self.command2, self.command1) + self.main_window.content = toga.Box() + + cropped = None + elif content_type == "window": + content.show() + + # image = self.main_window.screen.as_image() + # cropped = image.crop(... crop to window size ...) + # + # TODO: Crop the desktop image, rather than use a manual screenshot + await self.main_window.info_dialog( + "Manual intervention", + "Screenshot the secondary window, then press Done.", + ) + await self.manual_screenshot(toga.Box()) + cropped = None + + content.close() + + elif ( + content_type == "webview" + and toga.platform.current_platform == "macOS" + ): + # Manual screenshot required on macOS because webviews aren't + # rendered directly on the Window. + await self.main_window.info_dialog( + "Manual intervention", + "Screenshot the web widget content, then press Done.", + ) + await self.manual_screenshot(content) + cropped = None + else: + self.main_window.content = toga.Box( + children=[content], + style=Pack(direction=COLUMN), + ) + + await asyncio.sleep( + { + "webview": 4, + }.get(content_type, 2) + ) + image = Image.open(BytesIO(self.main_window.as_image().data)) + + scale_x = ( + image.size[0] + / self.main_window.content.layout.content_width + ) + scale_y = ( + image.size[1] + / self.main_window.content.layout.content_height + ) + + cropped = image.crop( + ( + 0, + 0, + (content.layout.content_width + 20) * scale_x, + (content.layout.content_height + 20) * scale_y, + ) + ) + + if cropped: + cropped.save( + self.app.paths.data + / f"{content_type}-{toga.platform.current_platform}.png" + ) + + except NotImplementedError: + pass + + def startup(self): + if toga.platform.current_platform in {"iOS", "android"}: + self.MAX_WIDTH = 370 + else: + self.MAX_WIDTH = 450 + + # Set up main window + self.main_window = toga.MainWindow(title="My Application") + + self.command1 = toga.Command( + lambda _: None, + text="Twist", + icon=toga.Icon.DEFAULT_ICON, + ) + self.command2 = toga.Command( + lambda _: None, + text="Shout", + icon="resources/brutus", + ) + + # Add the content on the main window + self.main_window.content = toga.Box() + + # Show the main window + self.main_window.show() + + self.add_background_task(self.sequence) + + +def main(): + return ScreenshotGeneratorApp("My Application", "org.beeware.widgets.screenshot") + + +if __name__ == "__main__": + app = main() + app.main_loop() diff --git a/examples/screenshot/screenshot/canvas.py b/examples/screenshot/screenshot/canvas.py new file mode 100644 index 0000000000..245e22042a --- /dev/null +++ b/examples/screenshot/screenshot/canvas.py @@ -0,0 +1,103 @@ +import math + +from toga.colors import REBECCAPURPLE, WHITE, rgb +from toga.constants import Baseline +from toga.fonts import SANS_SERIF, Font + + +def fill_head(canvas): + with canvas.Fill(color=rgb(149, 119, 73)) as head_filler: + head_filler.move_to(112, 103) + head_filler.line_to(112, 113) + head_filler.ellipse(73, 114, 39, 47, 0, 0, math.pi) + head_filler.line_to(35, 84) + head_filler.arc(65, 84, 30, math.pi, 3 * math.pi / 2) + head_filler.arc(82, 84, 30, 3 * math.pi / 2, 2 * math.pi) + + +def stroke_head(canvas): + with canvas.Stroke(line_width=4.0) as head_stroker: + with head_stroker.ClosedPath(112, 103) as closed_head: + closed_head.line_to(112, 113) + closed_head.ellipse(73, 114, 39, 47, 0, 0, math.pi) + closed_head.line_to(35, 84) + closed_head.arc(65, 84, 30, math.pi, 3 * math.pi / 2) + closed_head.arc(82, 84, 30, 3 * math.pi / 2, 2 * math.pi) + + +def draw_eyes(canvas): + with canvas.Fill(color=WHITE) as eye_whites: + eye_whites.arc(58, 92, 15) + eye_whites.arc(88, 92, 15, math.pi, 3 * math.pi) + + # Draw eyes separately to avoid miter join + with canvas.Stroke(line_width=4.0) as eye_outline: + eye_outline.arc(58, 92, 15) + with canvas.Stroke(line_width=4.0) as eye_outline: + eye_outline.arc(88, 92, 15, math.pi, 3 * math.pi) + + with canvas.Fill() as eye_pupils: + eye_pupils.arc(58, 97, 3) + eye_pupils.arc(88, 97, 3) + + +def draw_horns(canvas): + with canvas.Context() as r_horn: + with r_horn.Fill(color=rgb(212, 212, 212)) as r_horn_filler: + r_horn_filler.move_to(112, 99) + r_horn_filler.quadratic_curve_to(145, 65, 139, 36) + r_horn_filler.quadratic_curve_to(130, 60, 109, 75) + with r_horn.Stroke(line_width=4.0) as r_horn_stroker: + r_horn_stroker.move_to(112, 99) + r_horn_stroker.quadratic_curve_to(145, 65, 139, 36) + r_horn_stroker.quadratic_curve_to(130, 60, 109, 75) + + with canvas.Context() as l_horn: + with l_horn.Fill(color=rgb(212, 212, 212)) as l_horn_filler: + l_horn_filler.move_to(35, 99) + l_horn_filler.quadratic_curve_to(2, 65, 6, 36) + l_horn_filler.quadratic_curve_to(17, 60, 37, 75) + with l_horn.Stroke(line_width=4.0) as l_horn_stroker: + l_horn_stroker.move_to(35, 99) + l_horn_stroker.quadratic_curve_to(2, 65, 6, 36) + l_horn_stroker.quadratic_curve_to(17, 60, 37, 75) + + +def draw_nostrils(canvas): + with canvas.Fill(color=rgb(212, 212, 212)) as nose_filler: + nose_filler.move_to(45, 145) + nose_filler.bezier_curve_to(51, 123, 96, 123, 102, 145) + nose_filler.ellipse(73, 114, 39, 47, 0, math.pi / 4, 3 * math.pi / 4) + with canvas.Fill() as nostril_filler: + nostril_filler.arc(63, 140, 3) + nostril_filler.arc(83, 140, 3) + with canvas.Stroke(line_width=4.0) as nose_stroker: + nose_stroker.move_to(45, 145) + nose_stroker.bezier_curve_to(51, 123, 96, 123, 102, 145) + + +def draw_text(canvas): + font = Font(family=SANS_SERIF, size=20) + text_width, text_height = canvas.measure_text("Tiberius", font) + + x = (150 - text_width) // 2 + y = 175 + + with canvas.Stroke(color=REBECCAPURPLE, line_width=4.0) as rect_stroker: + rect_stroker.rect( + x - 5, + y - 5, + text_width + 10, + text_height + 10, + ) + with canvas.Fill(color=rgb(149, 119, 73)) as text_filler: + text_filler.write_text("Tiberius", x, y, font, Baseline.TOP) + + +def draw_tiberius(canvas): + fill_head(canvas) + draw_eyes(canvas) + draw_horns(canvas) + draw_nostrils(canvas) + stroke_head(canvas) + draw_text(canvas) diff --git a/examples/screenshot/screenshot/resources/README b/examples/screenshot/screenshot/resources/README new file mode 100644 index 0000000000..84f0abfa08 --- /dev/null +++ b/examples/screenshot/screenshot/resources/README @@ -0,0 +1 @@ +Put any icons or images in this directory. diff --git a/examples/screenshot/screenshot/resources/brutus.png b/examples/screenshot/screenshot/resources/brutus.png new file mode 100644 index 0000000000..39184054eb Binary files /dev/null and b/examples/screenshot/screenshot/resources/brutus.png differ diff --git a/examples/screenshot/screenshot/resources/user.png b/examples/screenshot/screenshot/resources/user.png new file mode 100644 index 0000000000..c8ca465165 Binary files /dev/null and b/examples/screenshot/screenshot/resources/user.png differ diff --git a/gtk/src/toga_gtk/images.py b/gtk/src/toga_gtk/images.py index 32bc735869..cf8534d1d5 100644 --- a/gtk/src/toga_gtk/images.py +++ b/gtk/src/toga_gtk/images.py @@ -25,6 +25,14 @@ def get_width(self): def get_height(self): return self.native.get_height() + def get_data(self): + success, buffer = self.native.save_to_bufferv("png") + if success: + return buffer + else: # pragma: nocover + # This shouldn't ever happen, and it's difficult to manufacture in test conditions + raise ValueError("Unable to get PNG data for image") + def save(self, path): path = Path(path) try: diff --git a/gtk/src/toga_gtk/widgets/numberinput.py b/gtk/src/toga_gtk/widgets/numberinput.py index c417867f9c..176825bf3a 100644 --- a/gtk/src/toga_gtk/widgets/numberinput.py +++ b/gtk/src/toga_gtk/widgets/numberinput.py @@ -67,5 +67,7 @@ def rehint(self): width = self.native.get_preferred_width() height = self.native.get_preferred_height() - self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH + width[1]) + self.interface.intrinsic.width = at_least( + max(self.interface._MIN_WIDTH, width[1]) + ) self.interface.intrinsic.height = height[1] diff --git a/gtk/src/toga_gtk/widgets/textinput.py b/gtk/src/toga_gtk/widgets/textinput.py index 09948dbebe..ecce9555d5 100644 --- a/gtk/src/toga_gtk/widgets/textinput.py +++ b/gtk/src/toga_gtk/widgets/textinput.py @@ -59,10 +59,12 @@ def rehint(self): # self._impl.get_preferred_width(), self._impl.get_preferred_height(), # getattr(self, '_fixed_height', False), getattr(self, '_fixed_width', False) # ) - # width = self.native.get_preferred_width() + width = self.native.get_preferred_width() height = self.native.get_preferred_height() - self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH) + self.interface.intrinsic.width = at_least( + max(self.interface._MIN_WIDTH, width[1]) + ) self.interface.intrinsic.height = height[1] def set_error(self, error_message): diff --git a/gtk/src/toga_gtk/window.py b/gtk/src/toga_gtk/window.py index da3db6e96c..32589bebfd 100644 --- a/gtk/src/toga_gtk/window.py +++ b/gtk/src/toga_gtk/window.py @@ -1,7 +1,7 @@ from toga.command import GROUP_BREAK, SECTION_BREAK from .container import TogaContainer -from .libs import Gtk +from .libs import Gdk, Gtk def gtk_toolbar_item_clicked(cmd): @@ -158,3 +158,32 @@ def set_full_screen(self, is_full_screen): self.native.fullscreen() else: self.native.unfullscreen() + + def get_image_data(self): + display = self.native.get_display() + display.flush() + + # For some reason, converting the *window* to a pixbuf fails. But if you extract + # a *part* of the overall screen, that works. So - work out the origin of the + # window, then the allocation for the container relative to that window, and + # capture that rectangle. + window = self.native.get_window() + origin = window.get_origin() + allocation = self.container.get_allocation() + + screen = display.get_default_screen() + root_window = screen.get_root_window() + screenshot = Gdk.pixbuf_get_from_window( + root_window, + origin.x + allocation.x, + origin.y + allocation.y, + allocation.width, + allocation.height, + ) + + success, buffer = screenshot.save_to_bufferv("png") + if success: + return buffer + else: # pragma: nocover + # This shouldn't ever happen, and it's difficult to manufacture in test conditions + raise ValueError(f"Unable to generate screenshot of {self}") diff --git a/gtk/tests_backend/probe.py b/gtk/tests_backend/probe.py index 597c0a5470..abb09434fd 100644 --- a/gtk/tests_backend/probe.py +++ b/gtk/tests_backend/probe.py @@ -20,3 +20,6 @@ async def redraw(self, message=None, delay=None): if delay: await asyncio.sleep(delay) + + def assert_image_size(self, image_size, size): + assert image_size == size diff --git a/gtk/tests_backend/widgets/canvas.py b/gtk/tests_backend/widgets/canvas.py index 391cdf04ec..81deb6caa9 100644 --- a/gtk/tests_backend/widgets/canvas.py +++ b/gtk/tests_backend/widgets/canvas.py @@ -19,10 +19,6 @@ def reference_variant(self, reference): def get_image(self): return Image.open(BytesIO(self.impl.get_image_data())) - def assert_image_size(self, image, width, height): - assert image.width == width - assert image.height == height - async def mouse_press(self, x, y): event = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS) event.button = 1 diff --git a/iOS/setup.py b/iOS/setup.py index f9c2736edd..30217c2e40 100644 --- a/iOS/setup.py +++ b/iOS/setup.py @@ -7,7 +7,7 @@ version=version, install_requires=[ "fonttools >= 4.42.1, < 5.0.0", - "rubicon-objc >= 0.4.5rc1, < 0.5.0", + "rubicon-objc >= 0.4.7, < 0.5.0", f"toga-core == {version}", ], ) diff --git a/iOS/src/toga_iOS/dialogs.py b/iOS/src/toga_iOS/dialogs.py index 917ca6597c..9216de940c 100644 --- a/iOS/src/toga_iOS/dialogs.py +++ b/iOS/src/toga_iOS/dialogs.py @@ -1,7 +1,6 @@ from abc import ABC, abstractmethod -from rubicon.objc import Block -from rubicon.objc.runtime import objc_id +from rubicon.objc import Block, objc_id from toga_iOS.libs import ( UIAlertAction, diff --git a/iOS/src/toga_iOS/images.py b/iOS/src/toga_iOS/images.py index c3da2eba8a..2d0c0cbf3e 100644 --- a/iOS/src/toga_iOS/images.py +++ b/iOS/src/toga_iOS/images.py @@ -1,3 +1,4 @@ +from ctypes import POINTER, c_char, cast from pathlib import Path from toga_iOS.libs import ( @@ -7,6 +8,15 @@ ) +def nsdata_to_bytes(data: NSData) -> bytes: + """Convert an NSData into a raw bytes representation""" + # data is an NSData object that has .bytes as a c_void_p, and a .length. Cast to + # POINTER(c_char) to get an addressable array of bytes, and slice that array to + # the known length. We don't use c_char_p because it has handling of NUL + # termination, and POINTER(c_char) allows array subscripting. + return cast(data.bytes, POINTER(c_char))[: data.length] + + class Image: def __init__(self, interface, path=None, data=None): self.interface = interface @@ -33,6 +43,9 @@ def get_width(self): def get_height(self): return self.native.size.height + def get_data(self): + return nsdata_to_bytes(NSData(uikit.UIImagePNGRepresentation(self.native))) + def save(self, path): path = Path(path) try: diff --git a/iOS/src/toga_iOS/libs/core_graphics.py b/iOS/src/toga_iOS/libs/core_graphics.py index 929f8f0d52..356fcd2419 100644 --- a/iOS/src/toga_iOS/libs/core_graphics.py +++ b/iOS/src/toga_iOS/libs/core_graphics.py @@ -198,3 +198,13 @@ class CGAffineTransform(Structure): def CGRectMake(x, y, w, h): return CGRect(CGPoint(x, y), CGSize(w, h)) + + +###################################################################### +# CGImage.h + +CGImageRef = c_void_p +register_preferred_encoding(b"^{CGImage=}", CGImageRef) + +core_graphics.CGImageCreateWithImageInRect.argtypes = [CGImageRef, CGRect] +core_graphics.CGImageCreateWithImageInRect.restype = CGImageRef diff --git a/iOS/src/toga_iOS/widgets/canvas.py b/iOS/src/toga_iOS/widgets/canvas.py index bf952cc53c..9fc213f304 100644 --- a/iOS/src/toga_iOS/widgets/canvas.py +++ b/iOS/src/toga_iOS/widgets/canvas.py @@ -1,4 +1,3 @@ -from ctypes import POINTER, c_char, cast from math import ceil from rubicon.objc import ( @@ -10,15 +9,16 @@ NSPoint, NSRect, NSSize, + objc_id, objc_method, objc_property, ) -from rubicon.objc.runtime import objc_id from travertino.size import at_least from toga.colors import BLACK, TRANSPARENT, color from toga.constants import Baseline, FillRule from toga_iOS.colors import native_color +from toga_iOS.images import nsdata_to_bytes from toga_iOS.libs import ( CGPathDrawingMode, CGRectMake, @@ -311,13 +311,9 @@ def render(context): self.native.bounds, afterScreenUpdates=True ) - data = renderer.PNGDataWithActions(Block(render, None, objc_id)) - - # data is an NSData object that has .bytes as a c_void_p, and a .length. Cast to - # POINTER(c_char) to get an addressable array of bytes, and slice that array to - # the known length. We don't use c_char_p because it has handling of NUL - # termination, and POINTER(c_char) allows array subscripting. - return cast(data.bytes, POINTER(c_char))[: data.length] + return nsdata_to_bytes( + renderer.PNGDataWithActions(Block(render, None, objc_id)) + ) # Rehint def rehint(self): diff --git a/iOS/src/toga_iOS/widgets/webview.py b/iOS/src/toga_iOS/widgets/webview.py index 8ed5d8844d..7ef4852485 100644 --- a/iOS/src/toga_iOS/widgets/webview.py +++ b/iOS/src/toga_iOS/widgets/webview.py @@ -1,5 +1,4 @@ -from rubicon.objc import objc_method, objc_property, py_from_ns -from rubicon.objc.runtime import objc_id +from rubicon.objc import objc_id, objc_method, objc_property, py_from_ns from travertino.size import at_least from toga.widgets.webview import JavaScriptResult diff --git a/iOS/src/toga_iOS/window.py b/iOS/src/toga_iOS/window.py index 942b2ddccd..7bd8bda595 100644 --- a/iOS/src/toga_iOS/window.py +++ b/iOS/src/toga_iOS/window.py @@ -1,8 +1,22 @@ +from rubicon.objc import ( + Block, + NSPoint, + NSRect, + NSSize, + objc_id, +) + from toga_iOS.container import RootContainer +from toga_iOS.images import nsdata_to_bytes from toga_iOS.libs import ( + NSData, UIColor, + UIGraphicsImageRenderer, + UIImage, UIScreen, UIWindow, + core_graphics, + uikit, ) @@ -100,3 +114,73 @@ def set_full_screen(self, is_full_screen): def close(self): pass + + def get_image_data(self): + # This is... baroque. + # + # The iOS root container has an offset at the top, because the root view + # flows *under* the title bar. We don't want this in the screenshot. + # + # You can render a view using UIView.drawViewHierarchyInRect(), which + # takes a rect defining the region to be captured. It needs to be + # invoked in a graphics rendering context, which is initialized with a + # size. You'd *think* that you could specify the size of the final + # output image, and then render a rectangle that has that size at any + # position offset you choose... but no. If you do this, you end up with + # the *full* view, scaled to fit the provided size of the graphics + # context, with the offset being used in reverse to offset the origin of + # the scaling function. I'm sure this is useful to someone, but it's not + # useful to us. + # + # So - we capture the *entire* view, then crop to remove the section at + # the top of the image. + # + # Of course, the screenshot functionality uses UIImage, and UIImage has + # tooling to convert into PNG format... but doesn't contain *crop* + # functionality. + # + # So, we need to convert from UIImage to CGImage, and use Core Graphics + # to crop the image. + # + # Except that UIImage works in scaled coordinate, and Core Graphics + # works in native coordinates, so we need to do a size transformation + # along the way. + # + # I need a drink. + + renderer = UIGraphicsImageRenderer.alloc().initWithSize( + self.container.native.bounds.size + ) + + def render(context): + self.container.native.drawViewHierarchyInRect( + self.container.native.bounds, afterScreenUpdates=True + ) + + # Render the full image + full_image = UIImage.imageWithData( + renderer.PNGDataWithActions(Block(render, None, objc_id)) + ) + + # Get the size of the actual content (offsetting for the header) in raw coordinates. + container_bounds = self.container.content.native.bounds + image_bounds = NSRect( + NSPoint( + container_bounds.origin.x * UIScreen.mainScreen.scale, + (container_bounds.origin.y + self.container.top_offset) + * UIScreen.mainScreen.scale, + ), + NSSize( + container_bounds.size.width * UIScreen.mainScreen.scale, + container_bounds.size.height * UIScreen.mainScreen.scale, + ), + ) + + # Crop the image, + cropped_image = core_graphics.CGImageCreateWithImageInRect( + full_image.CGImage, image_bounds + ) + # Convert back into a UIGraphics + final_image = UIImage.imageWithCGImage(cropped_image) + # Convert into PNG data. + return nsdata_to_bytes(NSData(uikit.UIImagePNGRepresentation(final_image))) diff --git a/iOS/tests_backend/probe.py b/iOS/tests_backend/probe.py index e600fb2e43..c3a4653d11 100644 --- a/iOS/tests_backend/probe.py +++ b/iOS/tests_backend/probe.py @@ -1,6 +1,6 @@ import asyncio -from toga_iOS.libs import NSRunLoop +from toga_iOS.libs import NSRunLoop, UIScreen class BaseProbe: @@ -17,3 +17,8 @@ async def redraw(self, message=None, delay=None): # Running at "normal" speed, we need to release to the event loop # for at least one iteration. `runUntilDate:None` does this. NSRunLoop.currentRunLoop.runUntilDate(None) + + def assert_image_size(self, image_size, size): + # Retina displays render images at a higher resolution than their reported size. + scale = int(UIScreen.mainScreen.scale) + assert image_size == (size[0] * scale, size[1] * scale) diff --git a/iOS/tests_backend/widgets/canvas.py b/iOS/tests_backend/widgets/canvas.py index 5d233f0862..45de11f784 100644 --- a/iOS/tests_backend/widgets/canvas.py +++ b/iOS/tests_backend/widgets/canvas.py @@ -4,7 +4,7 @@ from PIL import Image from rubicon.objc import NSObject, NSPoint, ObjCClass, objc_method -from toga_iOS.libs import UIScreen, UIView +from toga_iOS.libs import UIView from .base import SimpleProbe @@ -33,12 +33,6 @@ def reference_variant(self, reference): def get_image(self): return Image.open(BytesIO(self.impl.get_image_data())) - def assert_image_size(self, image, width, height): - # Retina displays render images at a higher resolution than their reported size. - scale = int(UIScreen.mainScreen.scale) - assert image.width == width * scale - assert image.height == height * scale - async def mouse_press(self, x, y): touch = MockTouch.alloc().init() touches = NSSet.setWithObject(touch) diff --git a/iOS/tests_backend/window.py b/iOS/tests_backend/window.py index e7c5d30b15..08f9a34295 100644 --- a/iOS/tests_backend/window.py +++ b/iOS/tests_backend/window.py @@ -1,6 +1,6 @@ import pytest -from toga_iOS.libs import UIWindow +from toga_iOS.libs import UIApplication, UIWindow from .probe import BaseProbe @@ -19,9 +19,14 @@ async def wait_for_window(self, message, minimize=False, full_screen=False): @property def content_size(self): + # Content height doesn't include the status bar or navigation bar. return ( self.native.contentView.frame.size.width, - self.native.contentView.frame.size.height, + self.native.contentView.frame.size.height + - ( + UIApplication.sharedApplication.statusBarFrame.size.height + + self.native.rootViewController.navigationBar.frame.size.height + ), ) async def close_info_dialog(self, dialog): diff --git a/testbed/tests/test_images.py b/testbed/tests/test_images.py index 7f9b9717d3..dd38d8e26b 100644 --- a/testbed/tests/test_images.py +++ b/testbed/tests/test_images.py @@ -47,6 +47,12 @@ async def test_data_image(app): assert image.width == 110 assert image.height == 30 + # Construct a second image from the first image's data + image2 = toga.Image(data=image.data) + + assert image2.width == 110 + assert image2.height == 30 + async def test_bad_image_data(app): "If data isn't a valid image, an error is raised" diff --git a/testbed/tests/test_window.py b/testbed/tests/test_window.py index e8adf8df2b..8b7fe877c0 100644 --- a/testbed/tests/test_window.py +++ b/testbed/tests/test_window.py @@ -480,6 +480,13 @@ async def test_full_screen(second_window, second_window_probe): assert second_window_probe.content_size == initial_content_size +async def test_as_image(main_window, main_window_probe): + """The window can be captured as a screenshot""" + + screenshot = main_window.as_image() + main_window_probe.assert_image_size(screenshot.size, main_window_probe.content_size) + + ######################################################################################## # Dialog tests ######################################################################################## diff --git a/testbed/tests/widgets/test_canvas.py b/testbed/tests/widgets/test_canvas.py index 717a7a8d17..a7b723ce0c 100644 --- a/testbed/tests/widgets/test_canvas.py +++ b/testbed/tests/widgets/test_canvas.py @@ -231,7 +231,7 @@ async def test_image_data(canvas, probe): # Cloned image is the right size. The platform may do DPI scaling; # let the probe determine the correct scaled size. - probe.assert_image_size(image, 200, 200) + probe.assert_image_size(image.size, (200, 200)) def assert_reference(probe, reference, threshold=0.0): diff --git a/testbed/tests/widgets/test_detailedlist.py b/testbed/tests/widgets/test_detailedlist.py index 60188db317..d729333af0 100644 --- a/testbed/tests/widgets/test_detailedlist.py +++ b/testbed/tests/widgets/test_detailedlist.py @@ -248,7 +248,7 @@ def add_row(event_widget, **kwargs): await probe.refresh_action() # It can take a couple of cycles for the refresh handler to fully execute; # impose a small delay to ensure it's been processed. - await probe.redraw("A refresh action has occurred") + await probe.redraw("A refresh action has occurred", delay=0.2) # New data has been added assert len(widget.data) == 101 diff --git a/winforms/src/toga_winforms/images.py b/winforms/src/toga_winforms/images.py index 359c760715..9417fc56e1 100644 --- a/winforms/src/toga_winforms/images.py +++ b/winforms/src/toga_winforms/images.py @@ -33,6 +33,11 @@ def get_width(self): def get_height(self): return self.native.Height + def get_data(self): + stream = MemoryStream() + self.native.Save(stream, ImageFormat.Png) + return stream.ToArray() + def save(self, path): path = Path(path) try: diff --git a/winforms/src/toga_winforms/window.py b/winforms/src/toga_winforms/window.py index d5e8fa010a..2eec14647e 100644 --- a/winforms/src/toga_winforms/window.py +++ b/winforms/src/toga_winforms/window.py @@ -1,5 +1,7 @@ import System.Windows.Forms as WinForms -from System.Drawing import Point, Size +from System.Drawing import Bitmap, Graphics, Point, Size +from System.Drawing.Imaging import ImageFormat +from System.IO import MemoryStream from toga.command import GROUP_BREAK, SECTION_BREAK @@ -179,3 +181,18 @@ def resize_content(self): self.native.ClientSize.Width, self.native.ClientSize.Height - vertical_shift, ) + + def get_image_data(self): + size = Size(self.native_content.Size.Width, self.native_content.Size.Height) + bitmap = Bitmap(size.Width, size.Height) + graphics = Graphics.FromImage(bitmap) + + graphics.CopyFromScreen( + self.native_content.PointToScreen(Point.Empty), + Point(0, 0), + size, + ) + + stream = MemoryStream() + bitmap.Save(stream, ImageFormat.Png) + return stream.ToArray() diff --git a/winforms/tests_backend/probe.py b/winforms/tests_backend/probe.py index 6f39139f2b..bc02d4bba2 100644 --- a/winforms/tests_backend/probe.py +++ b/winforms/tests_backend/probe.py @@ -48,3 +48,6 @@ async def type_character(self, char, *, shift=False, ctrl=False, alt=False): # same app. Unfortunately that makes it difficult to run tests in the # background. SendKeys.SendWait(key_code) + + def assert_image_size(self, image_size, size): + assert image_size == (size[0] * self.scale_factor, size[1] * self.scale_factor) diff --git a/winforms/tests_backend/widgets/canvas.py b/winforms/tests_backend/widgets/canvas.py index 465bf40fc8..c102b7d8f3 100644 --- a/winforms/tests_backend/widgets/canvas.py +++ b/winforms/tests_backend/widgets/canvas.py @@ -17,10 +17,6 @@ def reference_variant(self, reference): def get_image(self): return Image.open(BytesIO(self.impl.get_image_data())) - def assert_image_size(self, image, width, height): - assert image.width == width * self.scale_factor - assert image.height == height * self.scale_factor - async def mouse_press(self, x, y, **kwargs): self.native.OnMouseDown(self.mouse_event(x, y, **kwargs)) self.native.OnMouseUp(self.mouse_event(x, y, **kwargs)) diff --git a/winforms/tests_backend/window.py b/winforms/tests_backend/window.py index ec3bac6d48..5fc6ae3c94 100644 --- a/winforms/tests_backend/window.py +++ b/winforms/tests_backend/window.py @@ -41,8 +41,11 @@ def close(self): @property def content_size(self): return ( - self.native.ClientSize.Width / self.scale_factor, - self.native.ClientSize.Height / self.scale_factor, + (self.native.ClientSize.Width) / self.scale_factor, + ( + (self.native.ClientSize.Height - self.impl.top_bars_height()) + / self.scale_factor + ), ) @property