Open kloknibor opened 2 weeks ago
In this code example the same issue arises: import toga
import toga
from toga.style import Pack
from toga.style.pack import COLUMN
import asyncio
class App(toga.App):
def startup(self):
self.main_window = toga.MainWindow(title="Auto Timesheets Client")
asyncio.create_task(self.show_window())
async def show_window(self):
pizza = toga.Box(style=Pack(flex=1))
pasta = toga.Box(style=Pack(flex=1))
# Create a WebView and make it full-screen by setting flex
webview = toga.WebView(style=Pack(flex=1))
webview.url = "https://beeware.org"
pizza.add(webview)
# Create the OptionContainer with tabs
container = toga.OptionContainer(
content=[
toga.OptionItem("Pizza", pizza),
toga.OptionItem("Pasta", pasta),
]
)
# Set the main window's content
self.main_window.content = container
self.main_window.show()
def main():
# Initialize the application
return App(formal_name="Auto Timesheets Client", app_id="org.beeware.auto.timesheets")
When the making of the option container is in the synchronous startup it seems to work, e.g. with this code:
import toga
from toga.style import Pack
from toga.style.pack import COLUMN
import asyncio
class App(toga.App):
def startup(self):
self.main_window = toga.MainWindow(title="Auto Timesheets Client")
pizza = toga.Box(style=Pack(flex=1))
pasta = toga.Box(style=Pack(flex=1))
# Create a WebView and make it full-screen by setting flex
webview = toga.WebView(style=Pack(flex=1))
webview.url = "https://beeware.org"
pizza.add(webview)
# Create the OptionContainer with tabs
container = toga.OptionContainer(
content=[
toga.OptionItem("Pizza", pizza),
toga.OptionItem("Pasta", pasta),
]
)
# Set the main window's content
self.main_window.content = container
self.main_window.show()
def main():
# Initialize the application
return App(formal_name="Auto Timesheets Client", app_id="org.beeware.auto.timesheets")
Thanks for the report - that's definitely an odd one. What's especially odd is that it appears to be the native OptionContainer widget itself that has the problem - it looks like all the pieces are rendered correctly except the background on the tab bar itself. That suggests the issue is with the widget not correctly evaluating its internal size... but I'm not sure why that would be happening.
My initial guess is that it might be due to the main_window.show()
call being deferred until after the app has started. As you've noted, if you construct the widgets in the startup method, the problem doesn't appear; that suggests to me that the "initial layout" is getting something stuck with an initial size that is zero sized because it's not visible, and that is influencing the rendering of the OptionContainer. More investigation is definitely required.
In terms of an immediate workaround, I'd suggest constructing as much of the layout as you can in the startup method, and then only populate the content that needs to be async in the task. For example - you can create the web view with no content, and then populate the webview in the async task.
@freakboy3742 thanks for the quick reply as always! I tried something similar as you suggested but tried to create the window in the synchronous init function of a togabox I was creating async. With this solution I now see a clear race condition, sometimes it shows correctly, sometimes it doesn't.
For now I'll just move the constructing of the window in my startup function in app.py this should work, thanks!
I have rewritten my code to this, but I still have the same issue when I call _show_main. As you can see the content is already generated in the startup. Any more suggestions for workarounds?:
import toga
import platform
import ctypes
from toga.style import Pack
from toga.style.pack import COLUMN
class App(toga.App):
def startup(self):
# hardcoded sections
self.desktop_splash_width = 600
self.desktop_splash_height = 200
self.app_version = "Version 1.0.0"
self.test_mobile = True
# preparing all functionality for startup
# Detect platform and get screen size
self.platform = platform.system()
# Create the main window
self._create_main_window()
self.main_window.show()
self.screen_width, self.screen_height = self.get_screen_size()
# constructing content
self.splashscreen_container = self._construct_splash()
self.main_window_container = self._construct_main()
# Show a unified splash screen
self._show_splash()
def _create_main_window(self):
# Common main window logic
self.main_window = toga.MainWindow(title="Auto Timesheets Client")
def get_screen_size(self):
# Platform-specific screen size fetching
if self.platform == "Windows":
user32 = ctypes.windll.user32
screen_width = user32.GetSystemMetrics(0)
screen_height = user32.GetSystemMetrics(1)
return screen_width, screen_height
elif self.platform in ['iOS', 'iPadOS']:
return self.main_window.size
return None, None # Default case for non-dynamic platforms
def _construct_splash(self):
# Create a black background container for the splash screen
splashscreen_box = toga.Box(style=Pack(direction="column", background_color="black"))
# Import the unified splash screen widget
from .ui.widgets.splash_screen import SplashScreen
if platform.system() in ['iOS', 'iPadOS', 'Android']:
splash_screen = SplashScreen(self, screen_width=self.screen_width, screen_height=self.screen_height,
splash_width=self.screen_width, splash_height=self.screen_height,
version=self.app_version)
else:
# Calculate the center position for the splash screen
x_position = ((self.screen_width - self.desktop_splash_width) // 2)
y_position = ((self.screen_height - self.desktop_splash_height) // 2) - 150
# Setting the window size to smaller on startup
self.main_window.position = (x_position, y_position)
self.main_window.size = self.desktop_splash_width, self.desktop_splash_height
splash_screen = SplashScreen(self, screen_width=self.screen_width, screen_height=self.screen_height,
splash_width=self.desktop_splash_width,
splash_height=self.desktop_splash_height,
version=self.app_version)
splashscreen_box.add(splash_screen)
return splashscreen_box
def _construct_main(self):
if self.test_mobile or platform.system() in ['iOS', 'iPadOS', 'Android']:
# Do mobile imports
from autotimesheets.ui.mobile.widgets.timeline_window import MapWindow
from autotimesheets.ui.mobile.widgets.settings_window import SettingsWindow
from autotimesheets.ui.mobile.widgets.timesheet_window import TimesheetWindow
from autotimesheets.ui.mobile.widgets.web_window import WebWindow
# set screensize
if self.test_mobile:
self.app.main_window.size = (650, 1100)
else:
self.app.main_window.size = (self.screen_width, self.screen_height)
# making sure objects are accesable later
self.map_window = MapWindow(app=self.app)
# build mobile main window
mainwindow_container = toga.OptionContainer(
content=[
toga.OptionItem("Timeline", self.map_window),
toga.OptionItem("Timesheets", TimesheetWindow(app=self.app)),
toga.OptionItem("Web", WebWindow(app=self.app)),
toga.OptionItem("Settings", SettingsWindow(app=self.app))
]
)
return mainwindow_container
else:
from autotimesheets.ui.desktop import main_window
mainwindow_container = toga.SplitContainer(direction=Direction.VERTICAL,
style=Pack(direction="column", background_color="black"))
left_container = main_window.list_container(self.app)
right_container = main_window.map_container(self.app)
mainwindow_container.content = [left_container, right_container]
return mainwindow_container
def _show_splash(self):
self.main_window.content = self.splashscreen_container
def _show_main(self, workorders=None, timesheets=None):
# Set the main window's content
self.main_window.content = self.main_window_container
self.map_window.add_workorders(workorders)
print(workorders)
def main():
# Initialize the application
return App(formal_name="Auto Timesheets Client", app_id="org.beeware.auto.timesheets")
Also there is another bug, there seems to be a padding top between the mainwindow title on ios and the start of the option container. As can also be seen in the screenshots earlier, the white bar at the top.
It's very difficult to debug examples when you provide code for a complete app, rather than a reduced example - but the thing that stands out is that your initial main_window is being shown before it has any content, and the splash screen is being added after the window is shown - and it's not clear when _show_main
is being invoked at all. The design I was suggesting was that the main window content should be fully constructed and added to main window before the window is shown. It should be possible to swap content at any time; but that might point at the source of the bug if the initial content is empty/zero-sized, rather than being flexible.
(As an aside, I'll also note that the splash screen approach you're using based on hard-coding the size of the window isn't using the Toga API in the way it's intended to be used. If you need a layout to fill the window, then you need a flexible layout that fills the window, not content that is hard coded to a specific size).
Describe the bug
I have an app that on ios first loads a toga box, does load data than when finished opens a optioncontainer in the main window by changing the main_window.content.
When the option container first renders it overlays the webview behind. When I switch between different options the rendering will be fine after. I have tried to make a highly simplified version, but here all works as expected.
There is only one strange thing in my app, which is that I render a webview outside of the visible window to retrieve the data required.
Steps to reproduce
Expected behavior
I expect the webview inside the option of the option container to stay within the option container area and not overlap the tabs.
Screenshots
Environment
Logs
Additional context
No response