beeware / toga

A Python native, OS native GUI toolkit.
https://toga.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
4.36k stars 672 forks source link

Settings API for Toga #90

Open Ocupe opened 7 years ago

Ocupe commented 7 years ago

Settings API for Toga

I wrote this short text after a brief discussion with @freakboy3742. My intention is to kick off a conversation about a possible 'Settings API' for the toga project. This text is badly written and possibly full of bad ideas. But it is a start! :)

Settings API

The settings API is the attempt to provide us programmers with a abstract interface to easily create settings/preferences panels that work on all platforms. A Mac user should feel just as 'at home' as a windows or linux user. On top of that, it should be possible to port a desktop settings window to mobile without the need of rewriting it.

Native desktop settings in the wild

To get a better understanding about how native settings look like on different platforms I added this small overview.

Desktop Examples:

macos_settings

windows_settings

Mobile Examples:

ios_settings

android_settings

Basic Structure

I think that all settings representations, no matter on what platform, have a common structure. They try to group settings which are related into some kind of settings group. On desktop platforms they use some sort of tab functionality, on mobile the use nested table views. These groups are then again grouped into a settings menu. On desktop often represented in form of a single settings window and on mobile in form of a settings view which holds a table view with navigation.

Basic Hierarchy:

Write once, use everywhere

I see the future of the toga settings API as write one, use everywhere! The translation from a desktop settings window to a mobile settings table view shouldn't be the problem as long as all settings items are implemented on all platforms.

'Translations'

In this section I describes possible 'translations' from desktop to mobile.

on Mobile on Desktop
root view independent settings window
root structure TableView with subsections + Navigation Tabs
Settings Group TableView + Navigation Area of the active tab
Settings Items switch checkbox
slider slider
... ...

Settings Items

A list of possible settings items that one can use with the settings api. I don't see a problem if we allow the user to use all input widgets defined in the toga documentations. Again, as long as we make the settings items available on all platforms or find fitting 'translations' from desktop to mobile and vice versa.

Under the Hood

I don't know a clever way of implementing a Settings API. But at least I can share how I would like to interact with a potential Settings API and what things I would expect from it.

import toga

# Instantiating the settings class takes care of creating the settings window/view.
# All the platform specific bootstrapping should be taken care of and set to native defaults.
settings = toga.Settings()
# A the settings group should have a label and the possibility to add a icon to it.
group = toga.SettingsGroup('General'. icon='icon.png')

# creating setting item
switch = Switch(label='Show line numbers')

# adding setting item to the setting group
group.add(switch)

# adding group to settings
settings.add(group)

# you should be able to open the settings window/view by just calling the open() function.
settings.open()

Notes

freakboy3742 commented 7 years ago

Hells yes. This is exactly the sort of thing I had in my head! Thanks for the great writeup.

A couple more ideas to throw into your thoughts:

Ocupe commented 7 years ago

@freakboy3742 and everyone else. Before I run in the completely wrong direction I would like to get your feedback on this. Especially the part of 'The Normalized Settings Structure'

Ocupe commented 7 years ago

Lowest Common Denominator (work in progress)

In this section we really go deep and try to find the lowest common denominator for a general settings API. It is clear that the integration of settings on iOS and Android is similar but still different. Because we try to provide a common interface, that works for all platforms, we are left with the challenging and tedious job to compare all the existing API's and then find a good way to generalise them into a toga settings API.
The following table shows what platform specific 'things'/widgets your specified toga SettingsItem would resolves to. For example, when you specify a toga toggle, you get a Toggle Switch on iOS and something called SwitchPreference on Android.

Links to the official documentations:

toga iOS android macOS Windows Linux
toggle Toggle Switch Element CheckBoxPreference, SwitchPreference  
text field Text Field Element EditTextPreference  
pick one Radio Group Element ListPreference  
multi value Multi Value Element MultiSelectListPreference  
slider Slider Element  

The Normalized Settings Structure

This is a proposal how the interface between toga.core and the respective system integrations of toga could look like. The following idea is derived from the fact that iOS and android store their settings in a .plist and .xml files. I though we could translate the in code specified settings to a normalised from. This agreed upon normalized form than acts as the base of all the system specific integrations.

Example (simplified)
# defining the settings
settings = toga.Settings(version='1.0.0')
switch = toga.SettingsItem('switch', label='My Switch', default=True)
slider = toga.SettingsItem('slider', label='My Slider', default=5, min=0, max=10)
group = toga.SettingsGroup('My Settings', [switch, slider])
settings.add_group(group)

# When printing the normalized form we end up whit something like this.
pprint(settings.get_normal_form())
{'settings': 
    {
    'version': '1.0.0',
    'groups': 
        [{'group':
            {
            'title': 'My Settings',
            'items':
                [{  
                    'default': True,
                    'key': 'my_switch',
                    'label': 'My Switch',
                    'type': 'switch'},
                {   
                    'default': 5,
                    'key': 'my_slider',
                    'label': 'My Slider',
                    'max': 10,
                    'min': 0,
                    'type': 'slider'
                }]
            }
        }]
    }
}

This normalized form, here proposed as a dictionary, can now be passed to the platform specific integration of toga. As a nice side effect we end up with something that can easily be stored in a xml or similar file format and is platform independent.

freakboy3742 commented 7 years ago

YES - this looks almost exactly what I had in mind. No particular feedback, other than "MOAR OF THIS". 😸

Codep3 commented 2 days ago

Hi, I've created a proof of concept settings widget and data source here It uses the schema library and generates a data source which can be fed into the settings widget, validating user input before saving. I've only tried it on GTK and Textual so any comments would be appreciated.

freakboy3742 commented 2 days ago

Thanks for that contribution. I've taken a quick look, and while it looks like there's a good starting point there, there's a lot missing from the design discussion raised above.

  1. I'm not familiar with schema as a library; I can't rule it out, but there are other much better known schema management tools out there (pydantic and marshmallow being two that I'm familiar with)
  2. We're unlikely to use YAML as a default markup format, as it isn't supported in the standard library, and has myriad problems as a format (ranging from "Yes or Norway" to potential security vulnerabilities). TOML would be the most likely candidate, given the prominence of the format in the Python ecosystem.
  3. It looks like you're hard coding 2 types of input (text and numbers); that's definitely enough for a proof of concept, but there's clearly going to be a lot of other possibilities here, including booleans, selections from a static list, files, and possibly more. This suggests to me that there's a need for a separate piece of work related to form handling that can be used outside settings. Django's form framework is one example that could be used as inspiration here.
  4. Your code seems to stop at "can create a box full of widgets"; that's definitely part of what is required; but there's a lot more work needed to integrate settings into the app. A settings API for Toga means that the app has a settings menu item, and a settings panel that appears in a platform-appropriate location (which, on mobile, may not be in the app).

Obviously, none of this stops you from using your package externally (and thank you for using the togax- prefix on your package name); but if you're aiming to have that work merged into Toga's core, these are issues that will need to be addressed (and there's likely more that a detailed teardown would reveal).

Codep3 commented 14 hours ago

Thank you for taking a look, I appreciate it

  1. Thanks for the tips on pydantic and marshmallow, I'll take a look at these and see if they can be used. Do you have any preference on these? I selected schema as it seemed like a simple way to define the schema with no extra dependencies but I'm happy to change it.
  2. I chose YAML as I had some other projects using that. It shouldn't be a problem to swap this out, or have it as an option along with other formats.
  3. Yep, its just a proof of concept so I can see the extra items being added. But I thought I'd get feedback early, especially if I need to change the libraries as in point 1. And I'll take a look at the django form framework
  4. While the code does create the box of widgets, it also creates the data source for the settings which could be useful for the settings API, but again, this is just a POC. I don't really know much about iOS development and how the settings api would work on there, so I probably won't be able to work much on that right now.

I'll keep working on this as an external project for now, but the closer it stays to upstream standards, the better Thanks again, and thanks for the great project!

freakboy3742 commented 9 hours ago
  1. Thanks for the tips on pydantic and marshmallow, I'll take a look at these and see if they can be used. Do you have any preference on these? I selected schema as it seemed like a simple way to define the schema with no extra dependencies but I'm happy to change it.

I don't have a hard preference. If anything, my preference is really "can we do this without a third party dependency at all?". After all, this isn't a user-modifiable file - we're in control of reading and writing the file. In those conditions... what do we gain by using a schema library?

That said - of the candidates on the table, Pydantic is the one that I have seen getting the most attention of late - at least in part because it can also be used as the core of an ORM, as well as some other use cases. On that basis, it would be my (very weak) preference... although it also has a Rust-based binary component, and we don't currently have a compilation recipe for it on iOS or Android, so that complicates adoption. However, I'm also willing to be convinced of the merits of another option (based on feature set, filesize, community vitality etc).

I don't really know much about iOS development and how the settings api would work on there, so I probably won't be able to work much on that right now.

To be clear - we don't expect a contributor to implement every API for every platform. If nothing else, it's a rare developer who has a Mac, Windows, Linux, iOS and Android machine available for development and testing. We don't even expect a developer to become an expert the APIs of all those frameworks. However, we do need to have some degree of confidence that the API being proposed could be implemented on all platforms by someone who has access to the hardware and knowledge.

I'll keep working on this as an external project for now, but the closer it stays to upstream standards, the better Thanks again, and thanks for the great project!

Glad you're enjoying it!