feat: abandon UserControl as parent class for custom controls

since that is now deprecated; see also

- https://flet.dev/blog/flet-fastapi-and-async-api-improvements#custom-controls-api-normalized
- https://flet.dev/docs/getting-started/custom-controls/

also, the docs now have a good "counter" example which inspired a
change to the timestamp control, hopefully this means smarter thread
management and no more event loop errors..?
This commit is contained in:
Lance Edgar 2024-07-05 19:52:22 -05:00
parent 2a835d9bcb
commit 1df3327d9b
9 changed files with 345 additions and 357 deletions

View file

@ -1,58 +0,0 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar
#
# This file is part of WuttaPOS.
#
# WuttaPOS is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
WuttaPOS - custom controls (base class)
"""
import flet as ft
from wuttapos.util import make_button, show_snackbar
class WuttaControl(ft.UserControl):
def __init__(self, config, page=None, *args, **kwargs):
self.on_reset = kwargs.pop('on_reset', None)
super().__init__(*args, **kwargs)
self.config = config
self.app = config.get_app()
self.enum = self.app.enum
# TODO: why must we save this aside from self.page ?
# but sometimes self.page gets set to None, so we must..
self.mypage = page
def informed_refresh(self, **kwargs):
pass
def make_button(self, *args, **kwargs):
return make_button(*args, **kwargs)
def reset(self, e=None):
if self.on_reset:
self.on_reset(e=e)
def show_snackbar(self, text, bgcolor='yellow'):
show_snackbar(self.mypage, text, bgcolor=bgcolor)

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# WuttaPOS -- Pythonic Point of Sale System # WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar # Copyright © 2023-2024 Lance Edgar
# #
# This file is part of WuttaPOS. # This file is part of WuttaPOS.
# #
@ -28,34 +28,46 @@ import time
import flet as ft import flet as ft
from .base import WuttaControl
from .keyboard import WuttaKeyboard from .keyboard import WuttaKeyboard
from wuttapos.util import show_snackbar
class WuttaFeedback(WuttaControl): class WuttaFeedback(ft.Container):
default_font_size = 20 default_font_size = 20
default_button_height = 60 default_button_height = 60
def __init__(self, config, *args, **kwargs): def __init__(self, config, page=None, *args, **kwargs):
self.on_reset = kwargs.pop('on_reset', None)
self.on_send = kwargs.pop('on_send', None) self.on_send = kwargs.pop('on_send', None)
self.on_cancel = kwargs.pop('on_cancel', None) self.on_cancel = kwargs.pop('on_cancel', None)
super().__init__(config, *args, **kwargs) super().__init__(*args, **kwargs)
def build(self): self.config = config
self.app = config.get_app()
self.enum = self.app.enum
self.button = ft.Container(content=ft.Text("Feedback", size=self.default_font_size, # TODO: why must we save this aside from self.page ?
weight=ft.FontWeight.BOLD), # but sometimes self.page gets set to None, so we must..
height=self.default_button_height, self.mypage = page
width=self.default_button_height * 3,
on_click=self.initial_click,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
bgcolor='blue')
return self.button self.content = ft.Text("Feedback", size=self.default_font_size,
weight=ft.FontWeight.BOLD)
self.height = self.default_button_height
self.width = self.default_button_height * 3
self.on_click = self.initial_click
self.alignment = ft.alignment.center
self.border = ft.border.all(1, 'black')
self.border_radius = ft.border_radius.all(5)
self.bgcolor = 'blue'
def informed_refresh(self, **kwargs):
pass
def reset(self, e=None):
if self.on_reset:
self.on_reset(e=e)
def initial_click(self, e): def initial_click(self, e):
@ -155,7 +167,7 @@ class WuttaFeedback(WuttaControl):
}) })
self.dlg.open = False self.dlg.open = False
self.show_snackbar("MESSAGE WAS SENT", bgcolor='green') show_snackbar(self.mypage, "MESSAGE WAS SENT", bgcolor='green')
self.mypage.update() self.mypage.update()
if self.on_send: if self.on_send:

View file

@ -29,18 +29,23 @@ import rattail
import flet as ft import flet as ft
import wuttapos import wuttapos
from .base import WuttaControl
from .timestamp import WuttaTimestamp from .timestamp import WuttaTimestamp
from .feedback import WuttaFeedback from .feedback import WuttaFeedback
from wuttapos.util import make_button
class WuttaHeader(WuttaControl): class WuttaHeader(ft.Stack):
def __init__(self, *args, **kwargs): def __init__(self, config, page=None, *args, **kwargs):
self.terminal_id = kwargs.pop('terminal_id', None) self.terminal_id = kwargs.pop('terminal_id', None)
self.on_reset = kwargs.pop('on_reset', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def build(self): self.config = config
self.app = config.get_app()
self.enum = self.app.enum
self.txn_display = ft.Text("Txn: N", weight=ft.FontWeight.BOLD, size=20) self.txn_display = ft.Text("Txn: N", weight=ft.FontWeight.BOLD, size=20)
self.cust_display = ft.Text("Cust: N", weight=ft.FontWeight.BOLD, size=20) self.cust_display = ft.Text("Cust: N", weight=ft.FontWeight.BOLD, size=20)
@ -56,51 +61,52 @@ class WuttaHeader(WuttaControl):
terminal_style.bgcolor = 'red' terminal_style.bgcolor = 'red'
terminal_style.color = 'white' terminal_style.color = 'white'
return ft.Stack( self.controls = [
controls=[ ft.Container(
ft.Container( content=ft.Row(
content=ft.Row(
[
ft.Container(
content=self.training_mode,
bgcolor='yellow',
),
],
alignment=ft.MainAxisAlignment.CENTER,
),
),
ft.Row(
[ [
ft.Row( ft.Container(
[ content=self.training_mode,
self.txn_display, bgcolor='yellow',
ft.VerticalDivider(),
self.cust_display,
ft.VerticalDivider(),
WuttaTimestamp(self.config, weight=ft.FontWeight.BOLD, size=20),
],
),
ft.Row(
[
self.user_display,
ft.VerticalDivider(),
self.logout_button,
self.logout_divider,
ft.Text(
spans=[
ft.TextSpan(style=terminal_style, text=f"Term: {self.terminal_id or '??'}"),
],
),
ft.VerticalDivider(),
self.title_button,
],
), ),
], ],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN, alignment=ft.MainAxisAlignment.CENTER,
), ),
),
ft.Row(
[
ft.Row(
[
self.txn_display,
ft.VerticalDivider(),
self.cust_display,
ft.VerticalDivider(),
WuttaTimestamp(self.config, weight=ft.FontWeight.BOLD, size=20),
],
),
ft.Row(
[
self.user_display,
ft.VerticalDivider(),
self.logout_button,
self.logout_divider,
ft.Text(
spans=[
ft.TextSpan(style=terminal_style, text=f"Term: {self.terminal_id or '??'}"),
],
),
ft.VerticalDivider(),
self.title_button,
],
),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
]
], def reset(self, e=None):
) if self.on_reset:
self.on_reset(e=e)
def did_mount(self): def did_mount(self):
self.informed_refresh() self.informed_refresh()
@ -188,11 +194,11 @@ WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
has_perm = auth.has_permission(session, user, 'pos.test_error') has_perm = auth.has_permission(session, user, 'pos.test_error')
session.close() session.close()
if has_perm: if has_perm:
test_error = self.make_button("TEST ERROR", font_size=24, test_error = make_button("TEST ERROR", font_size=24,
height=60, height=60,
width=60 * 3, width=60 * 3,
bgcolor='red', bgcolor='red',
on_click=self.test_error_click) on_click=self.test_error_click)
buttons.append(test_error) buttons.append(test_error)
feedback = WuttaFeedback(self.config, page=self.page, feedback = WuttaFeedback(self.config, page=self.page,

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# WuttaPOS -- Pythonic Point of Sale System # WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar # Copyright © 2023-2024 Lance Edgar
# #
# This file is part of WuttaPOS. # This file is part of WuttaPOS.
# #
@ -26,20 +26,23 @@ WuttaPOS - keyboard control
import flet as ft import flet as ft
from .base import WuttaControl
class WuttaKeyboard(ft.Container):
class WuttaKeyboard(WuttaControl):
default_font_size = 20 default_font_size = 20
default_button_size = 80 default_button_size = 80
def __init__(self, *args, **kwargs): def __init__(self, config, page=None, *args, **kwargs):
self.on_reset = kwargs.pop('on_reset', None)
self.on_keypress = kwargs.pop('on_keypress', None) self.on_keypress = kwargs.pop('on_keypress', None)
self.on_long_backspace = kwargs.pop('on_long_backspace', None) self.on_long_backspace = kwargs.pop('on_long_backspace', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.config = config
self.app = config.get_app()
self.enum = self.app.enum
self.caps_lock = False self.caps_lock = False
self.caps_map = dict([(k, k.upper()) self.caps_map = dict([(k, k.upper())
for k in 'abcdefghijklmnopqrstuvwxyz']) for k in 'abcdefghijklmnopqrstuvwxyz'])
@ -69,6 +72,63 @@ class WuttaKeyboard(WuttaControl):
'/': '?', '/': '?',
} }
self.keys = {}
def make_key(key, data=None,
on_click=self.simple_keypress,
on_long_press=None,
width=self.default_button_size,
bgcolor=None):
button = ft.Container(content=ft.Text(key, size=self.default_font_size,
weight=ft.FontWeight.BOLD),
data=data or key,
height=self.default_button_size,
width=width,
on_click=on_click,
on_long_press=on_long_press,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
bgcolor=bgcolor,
)
self.keys[key] = button
return button
def caps_click(e):
self.update_caps_lock(not self.caps_lock)
self.caps_key = make_key('CAPS', on_click=caps_click)
if self.caps_lock:
self.caps_key.bgcolor = 'blue'
def shift_click(e):
self.update_shift(not self.shift)
self.shift_key = make_key('SHIFT', on_click=shift_click)
if self.shift:
self.shift_key.bgcolor = 'blue'
rows = [
[make_key(k) for k in "`1234567890-="] + [make_key('', bgcolor='yellow',
on_long_press=self.long_backspace)],
[make_key(k) for k in "qwertyuiop[]\\"],
[self.caps_key] + [make_key(k) for k in "asdfghjkl;'"] + [make_key('', bgcolor='blue')],
[self.shift_key] + [make_key(k) for k in "zxcvbnm,./"],
[make_key('SPACE', width=self.default_button_size * 5)],
]
rows = [ft.Row(controls, alignment=ft.MainAxisAlignment.CENTER)
for controls in rows]
self.content = ft.Column(rows)
def informed_refresh(self, **kwargs):
pass
def reset(self, e=None):
if self.on_reset:
self.on_reset(e=e)
def update_caps_lock(self, caps_lock): def update_caps_lock(self, caps_lock):
self.caps_lock = caps_lock self.caps_lock = caps_lock
@ -131,58 +191,3 @@ class WuttaKeyboard(WuttaControl):
def long_backspace(self, e): def long_backspace(self, e):
if self.on_long_backspace: if self.on_long_backspace:
self.on_long_backspace() self.on_long_backspace()
def build(self):
self.keys = {}
def make_key(key, data=None,
on_click=self.simple_keypress,
on_long_press=None,
width=self.default_button_size,
bgcolor=None):
button = ft.Container(content=ft.Text(key, size=self.default_font_size,
weight=ft.FontWeight.BOLD),
data=data or key,
height=self.default_button_size,
width=width,
on_click=on_click,
on_long_press=on_long_press,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
bgcolor=bgcolor,
)
self.keys[key] = button
return button
def caps_click(e):
self.update_caps_lock(not self.caps_lock)
self.caps_key = make_key('CAPS', on_click=caps_click)
if self.caps_lock:
self.caps_key.bgcolor = 'blue'
def shift_click(e):
self.update_shift(not self.shift)
self.shift_key = make_key('SHIFT', on_click=shift_click)
if self.shift:
self.shift_key.bgcolor = 'blue'
rows = [
[make_key(k) for k in "`1234567890-="] + [make_key('', bgcolor='yellow',
on_long_press=self.long_backspace)],
[make_key(k) for k in "qwertyuiop[]\\"],
[self.caps_key] + [make_key(k) for k in "asdfghjkl;'"] + [make_key('', bgcolor='blue')],
[self.shift_key] + [make_key(k) for k in "zxcvbnm,./"],
[make_key('SPACE', width=self.default_button_size * 5)],
]
rows = [ft.Row(controls, alignment=ft.MainAxisAlignment.CENTER)
for controls in rows]
return ft.Container(
content=ft.Column(
rows,
),
)

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# WuttaPOS -- Pythonic Point of Sale System # WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar # Copyright © 2023-2024 Lance Edgar
# #
# This file is part of WuttaPOS. # This file is part of WuttaPOS.
# #
@ -26,14 +26,15 @@ WuttaPOS - login form control
import flet as ft import flet as ft
from .base import WuttaControl
from .keyboard import WuttaKeyboard from .keyboard import WuttaKeyboard
from .tenkey import WuttaTenkeyMenu from .tenkey import WuttaTenkeyMenu
from wuttapos.util import make_button
class WuttaLoginForm(WuttaControl): class WuttaLoginForm(ft.Column):
def __init__(self, config, *args, **kwargs): def __init__(self, config, page=None, *args, **kwargs):
self.on_reset = kwargs.pop('on_reset', None)
# permission to be checked for login to succeed # permission to be checked for login to succeed
self.perm_required = kwargs.pop('perm_required', 'pos.ring_sales') self.perm_required = kwargs.pop('perm_required', 'pos.ring_sales')
@ -57,17 +58,21 @@ class WuttaLoginForm(WuttaControl):
self.on_authz_failure = kwargs.pop('on_authz_failure', None) self.on_authz_failure = kwargs.pop('on_authz_failure', None)
self.on_login_success = kwargs.pop('on_login_success', None) self.on_login_success = kwargs.pop('on_login_success', None)
super().__init__(config, *args, **kwargs) super().__init__(*args, **kwargs)
self.config = config
self.app = config.get_app()
self.enum = self.app.enum
# track which login input has focus # track which login input has focus
self.focused = None self.focused = None
def build(self):
login_form = self.build_login_form() login_form = self.build_login_form()
self.expand = True
self.alignment = ft.MainAxisAlignment.CENTER
if self.use_tenkey: if self.use_tenkey:
controls = [ self.controls = [
ft.Row( ft.Row(
[ [
login_form, login_form,
@ -81,7 +86,7 @@ class WuttaLoginForm(WuttaControl):
] ]
else: # full keyboard else: # full keyboard
controls = [ self.controls = [
login_form, login_form,
ft.Row(), ft.Row(),
ft.Row(), ft.Row(),
@ -90,9 +95,12 @@ class WuttaLoginForm(WuttaControl):
on_long_backspace=self.keyboard_long_backspace), on_long_backspace=self.keyboard_long_backspace),
] ]
return ft.Column(controls=controls, def informed_refresh(self, **kwargs):
expand=True, pass
alignment=ft.MainAxisAlignment.CENTER)
def reset(self, e=None):
if self.on_reset:
self.on_reset(e=e)
def build_login_form(self): def build_login_form(self):
form_fields = [] form_fields = []
@ -113,16 +121,16 @@ class WuttaLoginForm(WuttaControl):
form_fields.append(self.password) form_fields.append(self.password)
login_button = self.make_button("Login", login_button = make_button("Login",
height=60, height=60,
width=60 * 2.5, width=60 * 2.5,
bgcolor='blue', bgcolor='blue',
on_click=self.attempt_login) on_click=self.attempt_login)
reset_button = self.make_button("Clear", reset_button = make_button("Clear",
height=60, height=60,
width=60 * 2.5, width=60 * 2.5,
on_click=self.clear_login) on_click=self.clear_login)
if self.use_tenkey: if self.use_tenkey:
form_fields.extend([ form_fields.extend([

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# WuttaPOS -- Pythonic Point of Sale System # WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar # Copyright © 2023-2024 Lance Edgar
# #
# This file is part of WuttaPOS. # This file is part of WuttaPOS.
# #
@ -26,11 +26,11 @@ WuttaPOS - base lookup control
import flet as ft import flet as ft
from .base import WuttaControl
from .keyboard import WuttaKeyboard from .keyboard import WuttaKeyboard
from wuttapos.util import make_button
class WuttaLookup(WuttaControl): class WuttaLookup(ft.Container):
default_font_size = 40 default_font_size = 40
font_size = default_font_size * 0.8 font_size = default_font_size * 0.8
@ -39,7 +39,8 @@ class WuttaLookup(WuttaControl):
long_scroll_delta = 500 long_scroll_delta = 500
def __init__(self, *args, **kwargs): def __init__(self, config, page=None, *args, **kwargs):
self.on_reset = kwargs.pop('on_reset', None)
self.show_search = kwargs.pop('show_search', True) self.show_search = kwargs.pop('show_search', True)
self.initial_search = kwargs.pop('initial_search', None) self.initial_search = kwargs.pop('initial_search', None)
self.allow_empty_query = kwargs.pop('allow_empty_query', False) self.allow_empty_query = kwargs.pop('allow_empty_query', False)
@ -48,12 +49,14 @@ class WuttaLookup(WuttaControl):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.config = config
self.app = config.get_app()
self.enum = self.app.enum
# track current selection # track current selection
self.selected_uuid = None self.selected_uuid = None
self.selected_datarow = None self.selected_datarow = None
def build(self):
self.search_results = ft.DataTable( self.search_results = ft.DataTable(
columns=[ft.DataColumn(self.make_cell_text(text)) columns=[ft.DataColumn(self.make_cell_text(text))
for text in self.get_results_columns()], for text in self.get_results_columns()],
@ -64,27 +67,27 @@ class WuttaLookup(WuttaControl):
weight=ft.FontWeight.BOLD, weight=ft.FontWeight.BOLD,
visible=False) visible=False)
self.select_button = self.make_button("Select", self.select_button = make_button("Select",
font_size=self.font_size * 0.8, font_size=self.font_size * 0.8,
height=self.default_button_height_dlg * 0.8, height=self.default_button_height_dlg * 0.8,
width=self.default_button_height_dlg * 1.3, width=self.default_button_height_dlg * 1.3,
on_click=self.select_click, on_click=self.select_click,
disabled=True, disabled=True,
bgcolor=self.disabled_bgcolor) bgcolor=self.disabled_bgcolor)
self.up_button = self.make_button("", self.up_button = make_button("",
font_size=self.font_size, font_size=self.font_size,
height=self.default_button_height_dlg, height=self.default_button_height_dlg,
width=self.default_button_height_dlg, width=self.default_button_height_dlg,
on_click=self.up_click, on_click=self.up_click,
on_long_press=self.up_longpress) on_long_press=self.up_longpress)
self.down_button = self.make_button("", self.down_button = make_button("",
font_size=self.font_size, font_size=self.font_size,
height=self.default_button_height_dlg, height=self.default_button_height_dlg,
width=self.default_button_height_dlg, width=self.default_button_height_dlg,
on_click=self.down_click, on_click=self.down_click,
on_long_press=self.down_longpress) on_long_press=self.down_longpress)
self.search_results_wrapper = ft.Column( self.search_results_wrapper = ft.Column(
[ [
@ -110,18 +113,18 @@ class WuttaLookup(WuttaControl):
[ [
ft.Text("SEARCH FOR:"), ft.Text("SEARCH FOR:"),
self.searchbox, self.searchbox,
self.make_button("Lookup", make_button("Lookup",
font_size=self.font_size * 0.8, font_size=self.font_size * 0.8,
height=self.default_button_height_dlg * 0.8, height=self.default_button_height_dlg * 0.8,
width=self.default_button_height_dlg * 1.3, width=self.default_button_height_dlg * 1.3,
on_click=self.lookup, on_click=self.lookup,
bgcolor='blue'), bgcolor='blue'),
self.make_button("Reset", make_button("Reset",
font_size=self.font_size * 0.8, font_size=self.font_size * 0.8,
height=self.default_button_height_dlg * 0.8, height=self.default_button_height_dlg * 0.8,
width=self.default_button_height_dlg * 1.3, width=self.default_button_height_dlg * 1.3,
on_click=self.reset, on_click=self.reset,
bgcolor='yellow'), bgcolor='yellow'),
], ],
), ),
ft.Divider(), ft.Divider(),
@ -150,11 +153,11 @@ class WuttaLookup(WuttaControl):
ft.Row(), ft.Row(),
ft.Row(), ft.Row(),
ft.Row(), ft.Row(),
self.make_button("Cancel", make_button("Cancel",
font_size=self.font_size * 0.8, font_size=self.font_size * 0.8,
height=self.default_button_height_dlg * 0.8, height=self.default_button_height_dlg * 0.8,
width=self.default_button_height_dlg * 1.3, width=self.default_button_height_dlg * 1.3,
on_click=self.cancel), on_click=self.cancel),
], ],
), ),
], ],
@ -162,10 +165,15 @@ class WuttaLookup(WuttaControl):
), ),
]) ])
return ft.Container( self.content = ft.Column(controls=controls)
content=ft.Column(controls=controls), self.height = None if self.show_search else 600
height=None if self.show_search else 600,
) def informed_refresh(self, **kwargs):
pass
def reset(self, e=None):
if self.on_reset:
self.on_reset(e=e)
def get_results_columns(self): def get_results_columns(self):
raise NotImplementedError raise NotImplementedError

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# WuttaPOS -- Pythonic Point of Sale System # WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar # Copyright © 2023-2024 Lance Edgar
# #
# This file is part of WuttaPOS. # This file is part of WuttaPOS.
# #
@ -26,15 +26,16 @@ WuttaPOS - ten-key control
import flet as ft import flet as ft
from .base import WuttaControl from wuttapos.util import make_button
class WuttaTenkeyMenu(WuttaControl): class WuttaTenkeyMenu(ft.Container):
default_font_size = 40 default_font_size = 40
default_button_size = 100 default_button_size = 100
def __init__(self, *args, **kwargs): def __init__(self, config, page=None, *args, **kwargs):
self.on_reset = kwargs.pop('on_reset', None)
self.simple = kwargs.pop('simple', False) self.simple = kwargs.pop('simple', False)
self.on_char = kwargs.pop('on_char', None) self.on_char = kwargs.pop('on_char', None)
self.on_enter = kwargs.pop('on_enter', None) self.on_enter = kwargs.pop('on_enter', None)
@ -42,9 +43,12 @@ class WuttaTenkeyMenu(WuttaControl):
self.on_up_longpress = kwargs.pop('on_up_longpress', None) self.on_up_longpress = kwargs.pop('on_up_longpress', None)
self.on_down_click = kwargs.pop('on_down_click', None) self.on_down_click = kwargs.pop('on_down_click', None)
self.on_down_longpress = kwargs.pop('on_down_longpress', None) self.on_down_longpress = kwargs.pop('on_down_longpress', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def build(self): self.config = config
self.app = config.get_app()
self.enum = self.app.enum
row1 = [ row1 = [
self.make_tenkey_button("1"), self.make_tenkey_button("1"),
@ -90,30 +94,35 @@ class WuttaTenkeyMenu(WuttaControl):
self.make_tenkey_button("ENTER", width=self.default_button_size * 2), self.make_tenkey_button("ENTER", width=self.default_button_size * 2),
]) ])
return ft.Container( self.content = ft.Column(
content=ft.Column( [
[ ft.Row(
ft.Row( controls=row1,
controls=row1, spacing=0,
spacing=0, ),
), ft.Row(
ft.Row( controls=row2,
controls=row2, spacing=0,
spacing=0, ),
), ft.Row(
ft.Row( controls=row3,
controls=row3, spacing=0,
spacing=0, ),
), ft.Row(
ft.Row( controls=row4,
controls=row4, spacing=0,
spacing=0, ),
), ],
], spacing=0,
spacing=0,
),
) )
def informed_refresh(self, **kwargs):
pass
def reset(self, e=None):
if self.on_reset:
self.on_reset(e=e)
def make_tenkey_button( def make_tenkey_button(
self, self,
text, text,
@ -132,10 +141,10 @@ class WuttaTenkeyMenu(WuttaControl):
if not on_click: if not on_click:
on_click = self.tenkey_click on_click = self.tenkey_click
return self.make_button(text, font_size=font_size, bgcolor='green', return make_button(text, font_size=font_size, bgcolor='green',
height=height, width=width, height=height, width=width,
on_click=on_click, on_click=on_click,
on_long_press=on_long_press) on_long_press=on_long_press)
def tenkey_click(self, e): def tenkey_click(self, e):
value = e.control.content.value value = e.control.content.value

View file

@ -24,43 +24,35 @@
WuttaPOS - timestamp control WuttaPOS - timestamp control
""" """
import threading import asyncio
import time
import flet as ft import flet as ft
from .base import WuttaControl
class WuttaTimestamp(ft.Text):
class WuttaTimestamp(WuttaControl): def __init__(self, config, page=None, *args, **kwargs):
self.on_reset = kwargs.pop('on_reset', None)
def __init__(self, *args, **kwargs):
self.weight = kwargs.pop('weight', None)
self.size = kwargs.pop('size', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def build(self): self.config = config
text = self.render_time(self.app.localtime()) self.app = self.config.get_app()
self.display = ft.Text(text, weight=self.weight, size=self.size)
# nb. daemonized thread should be stopped when app exits self.value = self.render_time(self.app.localtime())
# cf. https://docs.python.org/3/library/threading.html#thread-objects
thread = threading.Thread(target=self.update_display, daemon=True)
thread.start()
return self.display def did_mount(self):
self.running = True
self.page.run_task(self.update_display)
def will_unmount(self):
self.running = False
def render_time(self, value): def render_time(self, value):
return value.strftime('%a %d %b %Y %I:%M:%S %p') return value.strftime('%a %d %b %Y %I:%M:%S %p')
def update_display(self): async def update_display(self):
while True: while self.running:
if self.page: self.value = self.render_time(self.app.localtime())
self.display.value = self.render_time(self.app.localtime()) self.update()
try: await asyncio.sleep(0.5)
self.update()
except RuntimeError as error:
if str(error) == 'Event loop is closed':
break
raise
time.sleep(0.5)

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# WuttaPOS -- Pythonic Point of Sale System # WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar # Copyright © 2024 Lance Edgar
# #
# This file is part of WuttaPOS. # This file is part of WuttaPOS.
# #
@ -26,21 +26,24 @@ WuttaPOS - txn item control
import flet as ft import flet as ft
from .base import WuttaControl
class WuttaTxnItem(ft.Row):
class WuttaTxnItem(WuttaControl):
""" """
Control for displaying a transaction line item within main POS Control for displaying a transaction line item within main POS
items list. items list.
""" """
font_size = 24 font_size = 24
def __init__(self, config, row, *args, **kwargs): def __init__(self, config, row, page=None, *args, **kwargs):
super().__init__(config, *args, **kwargs) self.on_reset = kwargs.pop('on_reset', None)
self.row = row
def build(self): super().__init__(*args, **kwargs)
self.config = config
self.app = config.get_app()
self.enum = self.app.enum
self.row = row
self.major_style = ft.TextStyle(size=self.font_size, self.major_style = ft.TextStyle(size=self.font_size,
weight=ft.FontWeight.BOLD) weight=ft.FontWeight.BOLD)
@ -50,11 +53,11 @@ class WuttaTxnItem(WuttaControl):
if self.row.row_type in (self.enum.POS_ROW_TYPE_SELL, if self.row.row_type in (self.enum.POS_ROW_TYPE_SELL,
self.enum.POS_ROW_TYPE_OPEN_RING): self.enum.POS_ROW_TYPE_OPEN_RING):
return self.build_item_sell() self.build_item_sell()
elif self.row.row_type in (self.enum.POS_ROW_TYPE_TENDER, elif self.row.row_type in (self.enum.POS_ROW_TYPE_TENDER,
self.enum.POS_ROW_TYPE_CHANGE_BACK): self.enum.POS_ROW_TYPE_CHANGE_BACK):
return self.build_item_tender() self.build_item_tender()
def build_item_sell(self): def build_item_sell(self):
@ -72,49 +75,52 @@ class WuttaTxnItem(WuttaControl):
# set initial text display values # set initial text display values
self.refresh(update=False) self.refresh(update=False)
return ft.Row( self.controls = [
[ ft.Text(
ft.Text( spans=[
spans=[ ft.TextSpan(f"{self.row.description}",
ft.TextSpan(f"{self.row.description}", style=self.major_style),
style=self.major_style), ft.TextSpan("× ", style=self.minor_style),
ft.TextSpan("× ", style=self.minor_style), self.quantity,
self.quantity, ft.TextSpan(" @ ", style=self.minor_style),
ft.TextSpan(" @ ", style=self.minor_style), self.txn_price,
self.txn_price, ],
], ),
), ft.Text(
ft.Text( spans=[
spans=[ self.fs_flag,
self.fs_flag, self.tax_flag,
self.tax_flag, self.sales_total,
self.sales_total, ],
], ),
),
], ]
alignment=ft.MainAxisAlignment.SPACE_BETWEEN, self.alignment = ft.MainAxisAlignment.SPACE_BETWEEN
)
def build_item_tender(self): def build_item_tender(self):
return ft.Row( self.controls = [
[ ft.Text(
ft.Text( spans=[
spans=[ ft.TextSpan(f"{self.row.description}",
ft.TextSpan(f"{self.row.description}", style=self.major_style),
style=self.major_style), ],
], ),
), ft.Text(
ft.Text( spans=[
spans=[ ft.TextSpan(self.app.render_currency(self.row.tender_total),
ft.TextSpan(self.app.render_currency(self.row.tender_total), style=self.major_style),
style=self.major_style), ],
], ),
),
], ]
alignment=ft.MainAxisAlignment.SPACE_BETWEEN, self.alignment = ft.MainAxisAlignment.SPACE_BETWEEN
)
def informed_refresh(self, **kwargs):
pass
def reset(self, e=None):
if self.on_reset:
self.on_reset(e=e)
def refresh(self, update=True): def refresh(self, update=True):