Skip to content

Instantly share code, notes, and snippets.

@falkoschindler
Created February 5, 2023 13:31
Show Gist options
  • Save falkoschindler/bc1eacad35ac455328110a4fcc33c3d0 to your computer and use it in GitHub Desktop.
Save falkoschindler/bc1eacad35ac455328110a4fcc33c3d0 to your computer and use it in GitHub Desktop.
Demonstrates a new style definition API using a builder pattern, literal keyword strings and re-usable style properties
#!/usr/bin/env python3
from copy import deepcopy
from typing import Literal, Optional, overload
from nicegui import ui
# This example demonstrates how to replace the style method with an instance of a Style class.
# The instance can be called like the original method.
# But it also has fields for manipulating individual style properties.
# Each field is an instance of a re-usable style property class with overloads for different units.
# For string values there are overloads with literal types for predefined values.
class ColorProperty:
def __init__(self, style: 'Style', key: str) -> None:
self.style = style
self.key = key
@overload
def __call__(self, color: Literal['red', 'green', 'blue', 'yellow', 'purple', 'orange', 'black', 'white', 'gray']) -> 'Style':
'''Use a predefined color name.'''
@overload
def __call__(self, value: str) -> 'Style':
'''Use an arbitrary value.'''
def __call__(self, value: str) -> 'Style':
self.style.element._style[self.key] = value
return self.style
class DimensionProperty:
def __init__(self, style: 'Style', key: str) -> None:
self.style = style
self.key = key
@overload
def __call__(self, px: float) -> 'Style':
'''Use a value in pixels.'''
@overload
def __call__(self, rem: float) -> 'Style':
'''Use a value in rem.'''
@overload
def __call__(self, percent: float) -> 'Style':
'''Use a value in percent.'''
@overload
def __call__(self, value: Literal['auto', 'max-content', 'min-content', 'fit-content']) -> 'Style':
'''Use a predefined value.'''
@overload
def __call__(self, value: str) -> 'Style':
'''Use an arbitrary value.'''
def __call__(self, value: str = ..., *, px: float = ..., rem: float = ..., percent: float = ...) -> 'Style':
if value is not ...:
self.style.element._style[self.key] = value
elif px is not ...:
self.style.element._style[self.key] = f'{px}px'
elif rem is not ...:
self.style.element._style[self.key] = f'{rem}rem'
elif percent is not ...:
self.style.element._style[self.key] = f'{percent}%'
else:
raise TypeError(f'{self.key} dimension requires either px, rem or percent')
return self.style
class Style:
def __init__(self, element: ui.element) -> None:
self.element = element
self.color = ColorProperty(self, 'color')
'''Set the color of the element.'''
self.background_color = ColorProperty(self, 'background-color')
'''Set the background color of the element.'''
self.width = DimensionProperty(self, 'width')
'''Set the width of the element.'''
self.height = DimensionProperty(self, 'height')
'''Set the height of the element.'''
self.margin = DimensionProperty(self, 'margin')
'''Set the margin of the element.'''
self.padding = DimensionProperty(self, 'padding')
'''Set the padding of the element.'''
def __call__(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None):
'''CSS style sheet definitions to modify the look of the element.
Every style in the `remove` parameter will be removed from the element.
Styles are separated with a semicolon.
This can be helpful if the predefined style sheet definitions by NiceGUI are not wanted in a particular styling.
'''
# NOTE: This is the original implementation of the style method.
style_dict = deepcopy(self.element._style) if replace is None else {}
for key in self.element._parse_style(remove):
if key in style_dict:
del style_dict[key]
style_dict.update(self.element._parse_style(add))
style_dict.update(self.element._parse_style(replace))
if self.element._style != style_dict:
self.element._style = style_dict
self.element.update()
return self.element
label = ui.label('Hello World!')
label.style = Style(label) # NOTE: monkey-patch label.style, will be done in ui.element
label.style.color('blue').background_color('orange').width(px=150).padding(rem=1).margin('auto')
ui.run(port=4321)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment