toga-iOS 0.4.2__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {toga-iOS-0.4.2/src/toga_iOS.egg-info → toga_ios-0.5.0}/PKG-INFO +10 -8
  2. {toga-iOS-0.4.2 → toga_ios-0.5.0}/README.rst +1 -1
  3. {toga-iOS-0.4.2 → toga_ios-0.5.0}/pyproject.toml +8 -7
  4. toga_ios-0.5.0/src/toga_iOS/__init__.py +3 -0
  5. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/app.py +68 -24
  6. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/colors.py +1 -2
  7. toga_ios-0.5.0/src/toga_iOS/command.py +28 -0
  8. toga_ios-0.5.0/src/toga_iOS/constraints.py +116 -0
  9. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/container.py +53 -5
  10. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/dialogs.py +39 -38
  11. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/factory.py +20 -4
  12. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/fonts.py +1 -1
  13. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/hardware/camera.py +7 -5
  14. toga_ios-0.5.0/src/toga_iOS/hardware/location.py +146 -0
  15. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/icons.py +5 -8
  16. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/images.py +0 -6
  17. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/libs/__init__.py +2 -0
  18. toga_ios-0.5.0/src/toga_iOS/libs/core_location.py +45 -0
  19. toga_ios-0.5.0/src/toga_iOS/libs/mapkit.py +63 -0
  20. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/libs/uikit.py +6 -1
  21. toga_ios-0.5.0/src/toga_iOS/screens.py +45 -0
  22. toga_ios-0.5.0/src/toga_iOS/statusicons.py +32 -0
  23. toga_ios-0.5.0/src/toga_iOS/widgets/activityindicator.py +31 -0
  24. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/base.py +13 -18
  25. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/box.py +7 -4
  26. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/button.py +1 -4
  27. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/canvas.py +12 -9
  28. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/detailedlist.py +2 -4
  29. toga_ios-0.5.0/src/toga_iOS/widgets/divider.py +43 -0
  30. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/imageview.py +0 -8
  31. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/label.py +7 -11
  32. toga_ios-0.5.0/src/toga_iOS/widgets/mapview.py +192 -0
  33. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/multilinetextinput.py +29 -32
  34. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/numberinput.py +1 -4
  35. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/scrollcontainer.py +4 -5
  36. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/selection.py +8 -5
  37. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/switch.py +6 -1
  38. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/textinput.py +24 -29
  39. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/webview.py +65 -4
  40. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/window.py +94 -39
  41. {toga-iOS-0.4.2 → toga_ios-0.5.0/src/toga_iOS.egg-info}/PKG-INFO +10 -8
  42. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS.egg-info/SOURCES.txt +14 -0
  43. toga_ios-0.5.0/src/toga_iOS.egg-info/requires.txt +3 -0
  44. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/app.py +26 -1
  45. toga_ios-0.5.0/tests_backend/dialogs.py +67 -0
  46. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/hardware/camera.py +9 -4
  47. toga_ios-0.5.0/tests_backend/hardware/location.py +185 -0
  48. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/icons.py +3 -0
  49. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/probe.py +6 -5
  50. toga_ios-0.5.0/tests_backend/screens.py +16 -0
  51. toga_ios-0.5.0/tests_backend/widgets/activityindicator.py +7 -0
  52. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/base.py +3 -3
  53. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/detailedlist.py +24 -10
  54. toga_ios-0.5.0/tests_backend/widgets/divider.py +7 -0
  55. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/label.py +5 -5
  56. toga_ios-0.5.0/tests_backend/widgets/mapview.py +102 -0
  57. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/numberinput.py +4 -4
  58. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/optioncontainer.py +5 -1
  59. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/properties.py +1 -1
  60. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/selection.py +5 -5
  61. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/switch.py +4 -1
  62. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/textinput.py +5 -5
  63. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/webview.py +3 -0
  64. toga_ios-0.5.0/tests_backend/window.py +75 -0
  65. toga-iOS-0.4.2/src/toga_iOS/__init__.py +0 -3
  66. toga-iOS-0.4.2/src/toga_iOS/command.py +0 -7
  67. toga-iOS-0.4.2/src/toga_iOS/constraints.py +0 -110
  68. toga-iOS-0.4.2/src/toga_iOS.egg-info/requires.txt +0 -3
  69. toga-iOS-0.4.2/tests_backend/window.py +0 -79
  70. {toga-iOS-0.4.2 → toga_ios-0.5.0}/CONTRIBUTING.md +0 -0
  71. {toga-iOS-0.4.2 → toga_ios-0.5.0}/LICENSE +0 -0
  72. {toga-iOS-0.4.2 → toga_ios-0.5.0}/setup.cfg +0 -0
  73. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/hardware/__init__.py +0 -0
  74. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/libs/av_foundation.py +0 -0
  75. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/libs/core_graphics.py +0 -0
  76. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/libs/core_text.py +0 -0
  77. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/libs/foundation.py +0 -0
  78. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/libs/webkit.py +0 -0
  79. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/paths.py +0 -0
  80. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/resources/optioncontainer-tab.png +0 -0
  81. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/resources/toga.icns +0 -0
  82. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/__init__.py +0 -0
  83. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/optioncontainer.py +0 -0
  84. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/passwordinput.py +0 -0
  85. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/progressbar.py +0 -0
  86. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS/widgets/slider.py +0 -0
  87. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS.egg-info/dependency_links.txt +0 -0
  88. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS.egg-info/entry_points.txt +0 -0
  89. {toga-iOS-0.4.2 → toga_ios-0.5.0}/src/toga_iOS.egg-info/top_level.txt +0 -0
  90. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/__init__.py +0 -0
  91. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/fonts.py +0 -0
  92. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/hardware/__init__.py +0 -0
  93. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/images.py +0 -0
  94. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/__init__.py +0 -0
  95. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/box.py +0 -0
  96. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/button.py +0 -0
  97. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/canvas.py +0 -0
  98. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/imageview.py +0 -0
  99. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/multilinetextinput.py +0 -0
  100. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/passwordinput.py +0 -0
  101. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/progressbar.py +0 -0
  102. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/scrollcontainer.py +0 -0
  103. {toga-iOS-0.4.2 → toga_ios-0.5.0}/tests_backend/widgets/slider.py +0 -0
@@ -1,36 +1,38 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: toga-iOS
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: An iOS backend for the Toga widget toolkit.
5
5
  Author-email: Russell Keith-Magee <russell@keith-magee.com>
6
6
  Maintainer-email: BeeWare Team <team@beeware.org>
7
7
  License: New BSD
8
8
  Project-URL: Homepage, https://beeware.org/project/projects/libraries/toga/
9
9
  Project-URL: Funding, https://beeware.org/contributing/membership/
10
- Project-URL: Documentation, https://toga.readthedocs.io/en/latest/
10
+ Project-URL: Documentation, https://toga.readthedocs.io/
11
11
  Project-URL: Tracker, https://github.com/beeware/toga/issues
12
12
  Project-URL: Source, https://github.com/beeware/toga
13
+ Project-URL: Changelog, https://toga.readthedocs.io/en/stable/background/project/releases.html
13
14
  Keywords: gui,widget,cross-platform,toga,mobile,iOS
14
15
  Classifier: Development Status :: 4 - Beta
15
16
  Classifier: Intended Audience :: Developers
16
17
  Classifier: License :: OSI Approved :: BSD License
17
18
  Classifier: Operating System :: OS Independent
18
19
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.8
20
20
  Classifier: Programming Language :: Python :: 3.9
21
21
  Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
24
25
  Classifier: Programming Language :: Python :: 3 :: Only
25
26
  Classifier: Topic :: Software Development
26
27
  Classifier: Topic :: Software Development :: User Interfaces
27
28
  Classifier: Topic :: Software Development :: Widget Sets
28
- Requires-Python: >=3.8
29
+ Requires-Python: >=3.9
29
30
  Description-Content-Type: text/x-rst
30
31
  License-File: LICENSE
31
32
  Requires-Dist: fonttools<5.0.0,>=4.42.1
32
- Requires-Dist: rubicon-objc<0.5.0,>=0.4.7
33
- Requires-Dist: toga-core==0.4.2
33
+ Requires-Dist: rubicon-objc<0.6.0,>=0.5.0
34
+ Requires-Dist: toga-core==0.5.0
35
+ Dynamic: requires-dist
34
36
 
35
37
  toga-iOS
36
38
  ========
@@ -40,7 +42,7 @@ An iOS backend for the `Toga widget toolkit`_.
40
42
  This package isn't much use by itself; it needs to be combined with `the core Toga library`_.
41
43
 
42
44
  For platform requirements, see the `iOS platform documentation
43
- <https://toga.readthedocs.io/en/stable/reference/platforms/iOS.html#prerequisites>`__.
45
+ <https://toga.readthedocs.io/en/latest/reference/platforms/iOS.html#prerequisites>`__.
44
46
 
45
47
  For more details, see the `Toga project on Github`_.
46
48
 
@@ -6,7 +6,7 @@ An iOS backend for the `Toga widget toolkit`_.
6
6
  This package isn't much use by itself; it needs to be combined with `the core Toga library`_.
7
7
 
8
8
  For platform requirements, see the `iOS platform documentation
9
- <https://toga.readthedocs.io/en/stable/reference/platforms/iOS.html#prerequisites>`__.
9
+ <https://toga.readthedocs.io/en/latest/reference/platforms/iOS.html#prerequisites>`__.
10
10
 
11
11
  For more details, see the `Toga project on Github`_.
12
12
 
@@ -1,8 +1,8 @@
1
1
  [build-system]
2
2
  requires = [
3
- "setuptools==69.0.0",
4
- "setuptools_scm==8.0.4",
5
- "setuptools_dynamic_dependencies @ git+https://github.com/beeware/setuptools_dynamic_dependencies",
3
+ "setuptools==76.0.0",
4
+ "setuptools_scm==8.2.0",
5
+ "setuptools_dynamic_dependencies==1.0.0",
6
6
  ]
7
7
  build-backend = "setuptools.build_meta"
8
8
 
@@ -11,7 +11,7 @@ dynamic = ["version", "dependencies"]
11
11
  name = "toga-iOS"
12
12
  description = "An iOS backend for the Toga widget toolkit."
13
13
  readme = "README.rst"
14
- requires-python = ">= 3.8"
14
+ requires-python = ">= 3.9"
15
15
  license.text = "New BSD"
16
16
  authors = [
17
17
  {name="Russell Keith-Magee", email="russell@keith-magee.com"},
@@ -33,11 +33,11 @@ classifiers = [
33
33
  "License :: OSI Approved :: BSD License",
34
34
  "Operating System :: OS Independent",
35
35
  "Programming Language :: Python :: 3",
36
- "Programming Language :: Python :: 3.8",
37
36
  "Programming Language :: Python :: 3.9",
38
37
  "Programming Language :: Python :: 3.10",
39
38
  "Programming Language :: Python :: 3.11",
40
39
  "Programming Language :: Python :: 3.12",
40
+ "Programming Language :: Python :: 3.13",
41
41
  "Programming Language :: Python :: 3 :: Only",
42
42
  "Topic :: Software Development",
43
43
  "Topic :: Software Development :: User Interfaces",
@@ -47,9 +47,10 @@ classifiers = [
47
47
  [project.urls]
48
48
  Homepage = "https://beeware.org/project/projects/libraries/toga/"
49
49
  Funding = "https://beeware.org/contributing/membership/"
50
- Documentation = "https://toga.readthedocs.io/en/latest/"
50
+ Documentation = "https://toga.readthedocs.io/"
51
51
  Tracker = "https://github.com/beeware/toga/issues"
52
52
  Source = "https://github.com/beeware/toga"
53
+ Changelog = "https://toga.readthedocs.io/en/stable/background/project/releases.html"
53
54
 
54
55
  [project.entry-points."toga.backends"]
55
56
  iOS = "toga_iOS"
@@ -60,7 +61,7 @@ root = ".."
60
61
  [tool.setuptools_dynamic_dependencies]
61
62
  dependencies = [
62
63
  "fonttools >= 4.42.1, < 5.0.0",
63
- "rubicon-objc >= 0.4.7, < 0.5.0",
64
+ "rubicon-objc >= 0.5.0, < 0.6.0",
64
65
  "toga-core == {version}",
65
66
  ]
66
67
 
@@ -0,0 +1,3 @@
1
+ import travertino
2
+
3
+ __version__ = travertino._package_version(__file__, __name__)
@@ -3,30 +3,32 @@ import asyncio
3
3
  from rubicon.objc import objc_method
4
4
  from rubicon.objc.eventloop import EventLoopPolicy, iOSLifecycle
5
5
 
6
- from toga_iOS.libs import UIResponder, av_foundation
7
- from toga_iOS.window import Window
6
+ import toga
7
+ from toga_iOS.libs import UIResponder, UIScreen, av_foundation
8
8
 
9
-
10
- class MainWindow(Window):
11
- _is_main_window = True
9
+ from .screens import Screen as ScreenImpl
12
10
 
13
11
 
14
12
  class PythonAppDelegate(UIResponder):
15
13
  @objc_method
16
14
  def applicationDidBecomeActive_(self, application) -> None:
17
15
  print("App became active.")
16
+ App.app.interface.current_window.on_gain_focus()
18
17
 
19
18
  @objc_method
20
19
  def applicationWillResignActive_(self, application) -> None:
21
20
  print("App about to leave foreground.", flush=True)
21
+ App.app.interface.current_window.on_lose_focus()
22
22
 
23
23
  @objc_method
24
24
  def applicationDidEnterBackground_(self, application) -> None:
25
25
  print("App entered background.")
26
+ App.app.interface.current_window.on_hide()
26
27
 
27
28
  @objc_method
28
29
  def applicationWillEnterForeground_(self, application) -> None:
29
30
  print("App about to enter foreground.")
31
+ App.app.interface.current_window.on_show()
30
32
 
31
33
  @objc_method
32
34
  def application_didFinishLaunchingWithOptions_(
@@ -35,6 +37,7 @@ class PythonAppDelegate(UIResponder):
35
37
  print("App finished launching.")
36
38
  App.app.native = application
37
39
  App.app.create()
40
+ App.app.interface.current_window.on_show()
38
41
  return True
39
42
 
40
43
  @objc_method
@@ -51,6 +54,12 @@ class PythonAppDelegate(UIResponder):
51
54
 
52
55
 
53
56
  class App:
57
+ # iOS apps exit when the last window is closed
58
+ CLOSE_ON_LAST_WINDOW = True
59
+ # iOS doesn't have command line handling;
60
+ # but saying it does shortcuts the default handling
61
+ HANDLES_COMMAND_LINE = True
62
+
54
63
  def __init__(self, interface):
55
64
  self.interface = interface
56
65
  self.interface._impl = self
@@ -67,14 +76,25 @@ class App:
67
76
  """Calls the startup method on the interface."""
68
77
  self.interface._startup()
69
78
 
70
- def open_document(self, fileURL): # pragma: no cover
71
- """Add a new document to this app."""
79
+ ######################################################################
80
+ # Commands and menus
81
+ ######################################################################
82
+
83
+ def create_standard_commands(self):
72
84
  pass
73
85
 
74
86
  def create_menus(self):
75
87
  # No menus on an iOS app (for now)
76
88
  pass
77
89
 
90
+ ######################################################################
91
+ # App lifecycle
92
+ ######################################################################
93
+
94
+ def exit(self): # pragma: no cover
95
+ # Mobile apps can't be exited, but the entry point needs to exist
96
+ pass
97
+
78
98
  def main_loop(self):
79
99
  # Main loop is non-blocking on iOS. The app loop is integrated with the
80
100
  # main iOS event loop, so this call will return; however, it will leave
@@ -82,36 +102,48 @@ class App:
82
102
  # iOS event loop.
83
103
  self.loop.run_forever_cooperatively(lifecycle=iOSLifecycle())
84
104
 
105
+ def set_icon(self, icon):
106
+ # iOS apps don't have runtime icons, so this can't be invoked
107
+ pass # pragma: no cover
108
+
85
109
  def set_main_window(self, window):
86
- pass
110
+ if window is None or window == toga.App.BACKGROUND:
111
+ raise ValueError("Apps without main windows are not supported on iOS")
87
112
 
88
- def get_current_window(self):
89
- # iOS only has a main window.
90
- return self.interface.main_window._impl
113
+ ######################################################################
114
+ # App resources
115
+ ######################################################################
91
116
 
92
- def set_current_window(self, window):
93
- # iOS only has a main window, so this is a no-op
94
- pass
117
+ def get_screens(self):
118
+ return [ScreenImpl(UIScreen.mainScreen)]
95
119
 
96
- def show_about_dialog(self):
97
- self.interface.factory.not_implemented("App.show_about_dialog()")
120
+ ######################################################################
121
+ # App state
122
+ ######################################################################
123
+
124
+ def get_dark_mode_state(self):
125
+ self.interface.factory.not_implemented("dark mode state")
126
+ return None
127
+
128
+ ######################################################################
129
+ # App capabilities
130
+ ######################################################################
98
131
 
99
132
  def beep(self):
100
133
  # 1013 is a magic constant that is the "SMS RECEIVED 5" sound,
101
134
  # sounding like a single strike of a bell.
102
135
  av_foundation.AudioServicesPlayAlertSound(1013)
103
136
 
104
- def exit(self): # pragma: no cover
105
- # Mobile apps can't be exited, but the entry point needs to exist
137
+ def open_document(self, fileURL): # pragma: no cover
138
+ """Add a new document to this app."""
106
139
  pass
107
140
 
108
- def enter_full_screen(self, windows):
109
- # No-op; mobile doesn't support full screen
110
- pass
141
+ def show_about_dialog(self):
142
+ self.interface.factory.not_implemented("App.show_about_dialog()")
111
143
 
112
- def exit_full_screen(self, windows):
113
- # No-op; mobile doesn't support full screen
114
- pass
144
+ ######################################################################
145
+ # Cursor control
146
+ ######################################################################
115
147
 
116
148
  def hide_cursor(self):
117
149
  # No-op; mobile doesn't support cursors
@@ -120,3 +152,15 @@ class App:
120
152
  def show_cursor(self):
121
153
  # No-op; mobile doesn't support cursors
122
154
  pass
155
+
156
+ ######################################################################
157
+ # Window control
158
+ ######################################################################
159
+
160
+ def get_current_window(self):
161
+ # iOS only has a main window.
162
+ return self.interface.main_window._impl
163
+
164
+ def set_current_window(self, window):
165
+ # iOS only has a main window, so this is a no-op
166
+ pass
@@ -11,10 +11,9 @@ def native_color(c):
11
11
  try:
12
12
  color = CACHE[c]
13
13
  except KeyError:
14
- # Color needs to be retained to be kept in the cache
15
14
  color = UIColor.colorWithRed(
16
15
  c.rgba.r / 255, green=c.rgba.g / 255, blue=c.rgba.b / 255, alpha=c.rgba.a
17
- ).retain()
16
+ )
18
17
  CACHE[c] = color
19
18
 
20
19
  return color
@@ -0,0 +1,28 @@
1
+ from toga import Command as StandardCommand
2
+
3
+
4
+ class Command:
5
+ def __init__(self, interface):
6
+ self.interface = interface
7
+ self.native = []
8
+
9
+ @classmethod
10
+ def standard(cls, app, id):
11
+ if id in {
12
+ StandardCommand.ABOUT,
13
+ StandardCommand.EXIT,
14
+ StandardCommand.NEW,
15
+ StandardCommand.OPEN,
16
+ StandardCommand.PREFERENCES,
17
+ StandardCommand.SAVE,
18
+ StandardCommand.SAVE_AS,
19
+ StandardCommand.SAVE_ALL,
20
+ StandardCommand.VISIT_HOMEPAGE,
21
+ }:
22
+ # These are valid commands, but they're not defined on iOS.
23
+ return None
24
+
25
+ raise ValueError(f"Unknown standard command {id!r}")
26
+
27
+ def set_enabled(self, value):
28
+ pass
@@ -0,0 +1,116 @@
1
+ from toga_iOS.libs import (
2
+ NSLayoutAttributeBottom,
3
+ NSLayoutAttributeLeft,
4
+ NSLayoutAttributeRight,
5
+ NSLayoutAttributeTop,
6
+ NSLayoutConstraint,
7
+ NSLayoutRelationEqual,
8
+ )
9
+
10
+
11
+ class Constraints:
12
+ def __init__(self, widget):
13
+ """A wrapper object storing the constraints required to position a widget at a
14
+ precise location in its container.
15
+
16
+ :param widget: The Widget implementation to be constrained.
17
+ """
18
+ self.widget = widget
19
+ self.widget.native.translatesAutoresizingMaskIntoConstraints = False
20
+
21
+ self._container = None
22
+
23
+ self.width_constraint = None
24
+ self.height_constraint = None
25
+
26
+ self.left_constraint = None
27
+ self.top_constraint = None
28
+
29
+ # Deletion isn't an event we can programmatically invoke; deletion
30
+ # of constraints can take several iterations before it occurs.
31
+ def __del__(self): # pragma: nocover
32
+ self._remove_constraints()
33
+
34
+ def _remove_constraints(self):
35
+ if self.container:
36
+ # print(f"Remove constraints for {self.widget} in {self.container}")
37
+ # Due to the unpredictability of garbage collection, it's possible for
38
+ # the native object of the window's container to be deleted on the ObjC
39
+ # side before the constraints for the window have been removed. Protect
40
+ # against this possibility.
41
+ if self.container.native:
42
+ self.container.native.removeConstraint(self.width_constraint)
43
+ self.container.native.removeConstraint(self.height_constraint)
44
+ self.container.native.removeConstraint(self.left_constraint)
45
+ self.container.native.removeConstraint(self.top_constraint)
46
+
47
+ @property
48
+ def container(self):
49
+ return self._container
50
+
51
+ @container.setter
52
+ def container(self, value):
53
+ # This will *always* remove and then add constraints. It relies on the base
54
+ # widget to *not* invoke this setter unless the container is actually changing.
55
+
56
+ self._remove_constraints()
57
+ self._container = value
58
+ if value is not None:
59
+ # print(
60
+ # f"Add constraints for {self.widget} in {self.container}"
61
+ # f"{self.widget.interface.layout}"
62
+ # )
63
+ self.left_constraint = NSLayoutConstraint.constraintWithItem(
64
+ self.widget.native,
65
+ attribute__1=NSLayoutAttributeLeft,
66
+ relatedBy=NSLayoutRelationEqual,
67
+ toItem=self.container.native,
68
+ attribute__2=NSLayoutAttributeLeft,
69
+ multiplier=1.0,
70
+ constant=10, # Use a dummy, non-zero value for now
71
+ )
72
+ self.container.native.addConstraint(self.left_constraint)
73
+
74
+ self.top_constraint = NSLayoutConstraint.constraintWithItem(
75
+ self.widget.native,
76
+ attribute__1=NSLayoutAttributeTop,
77
+ relatedBy=NSLayoutRelationEqual,
78
+ toItem=self.container.native,
79
+ attribute__2=NSLayoutAttributeTop,
80
+ multiplier=1.0,
81
+ constant=5, # Use a dummy, non-zero value for now
82
+ )
83
+ self.container.native.addConstraint(self.top_constraint)
84
+
85
+ self.width_constraint = NSLayoutConstraint.constraintWithItem(
86
+ self.widget.native,
87
+ attribute__1=NSLayoutAttributeRight,
88
+ relatedBy=NSLayoutRelationEqual,
89
+ toItem=self.widget.native,
90
+ attribute__2=NSLayoutAttributeLeft,
91
+ multiplier=1.0,
92
+ constant=50, # Use a dummy, non-zero value for now
93
+ )
94
+ self.container.native.addConstraint(self.width_constraint)
95
+
96
+ self.height_constraint = NSLayoutConstraint.constraintWithItem(
97
+ self.widget.native,
98
+ attribute__1=NSLayoutAttributeBottom,
99
+ relatedBy=NSLayoutRelationEqual,
100
+ toItem=self.widget.native,
101
+ attribute__2=NSLayoutAttributeTop,
102
+ multiplier=1.0,
103
+ constant=30, # Use a dummy, non-zero value for now
104
+ )
105
+ self.container.native.addConstraint(self.height_constraint)
106
+
107
+ def update(self, x, y, width, height):
108
+ # print(
109
+ # f"UPDATE CONSTRAINTS {self.widget} in {self.container} "
110
+ # f"{width}x{height}@{x},{y}"
111
+ # )
112
+ self.left_constraint.constant = x
113
+ self.top_constraint.constant = y
114
+
115
+ self.width_constraint.constant = width
116
+ self.height_constraint.constant = height
@@ -72,13 +72,18 @@ class Container(BaseContainer):
72
72
 
73
73
  self.layout_native = self.native if layout_native is None else layout_native
74
74
 
75
+ def __del__(self):
76
+ # Mark the contained native object as explicitly None so that the
77
+ # constraints know the object has been deleted.
78
+ self.native = None
79
+
75
80
  @property
76
81
  def width(self):
77
82
  return self.layout_native.bounds.size.width
78
83
 
79
84
  @property
80
85
  def height(self):
81
- return self.layout_native.bounds.size.height
86
+ return self.layout_native.bounds.size.height - self.top_offset
82
87
 
83
88
  @property
84
89
  def top_offset(self):
@@ -124,7 +129,54 @@ class RootContainer(Container):
124
129
  layout_native=None,
125
130
  on_refresh=None,
126
131
  ):
132
+ """A bare content container.
133
+
134
+ This is a container that *doesn't* include a navigation/title bar at the top.
135
+
136
+ :param content: The widget impl that is the container's initial content.
137
+ :param layout_native: The native widget that should be used to provide
138
+ size hints to the layout. This will usually be the container widget
139
+ itself; however, for widgets like ScrollContainer where the layout
140
+ needs to be computed based on a different size to what will be
141
+ rendered, the source of the size can be different.
142
+ :param on_refresh: The callback to be notified when this container's layout is
143
+ refreshed.
127
144
  """
145
+ super().__init__(
146
+ content=content,
147
+ layout_native=layout_native,
148
+ on_refresh=on_refresh,
149
+ )
150
+
151
+ # Construct a UIViewController to hold the root content
152
+ self.controller = UIViewController.alloc().init()
153
+
154
+ # Set the controller's view to be the root content widget
155
+ self.controller.view = self.native
156
+
157
+ # The testbed app won't instantiate a simple app, so we can't test these properties
158
+ @property
159
+ def top_offset(self): # pragma: no cover
160
+ return UIApplication.sharedApplication.statusBarFrame.size.height
161
+
162
+ @property
163
+ def title(self): # pragma: no cover
164
+ return self._title
165
+
166
+ @title.setter
167
+ def title(self, value):
168
+ self._title = value
169
+
170
+
171
+ class NavigationContainer(Container):
172
+ def __init__(
173
+ self,
174
+ content=None,
175
+ layout_native=None,
176
+ on_refresh=None,
177
+ ):
178
+ """A top level container that provides a navigation/title bar.
179
+
128
180
  :param content: The widget impl that is the container's initial content.
129
181
  :param layout_native: The native widget that should be used to provide
130
182
  size hints to the layout. This will usually be the container widget
@@ -151,10 +203,6 @@ class RootContainer(Container):
151
203
  # Set the controller's view to be the root content widget
152
204
  self.content_controller.view = self.native
153
205
 
154
- @property
155
- def height(self):
156
- return self.layout_native.bounds.size.height - self.top_offset
157
-
158
206
  @property
159
207
  def top_offset(self):
160
208
  return (