feat: Control widget for numeric fields

Change-Id: I00680f69dab06dedafde990dba3de1eeb994164a
diff --git a/test/test_config_ui.py b/test/test_config_ui.py
index 688f3ca..7b23027 100644
--- a/test/test_config_ui.py
+++ b/test/test_config_ui.py
@@ -7,15 +7,16 @@
 import unittest
 from typing import List
 from pydantic import conint, confloat
-from tkinter import ttk
+from tkinter import Tk, ttk
 from enum import IntEnum
+import toml
 
 from dcs.config.store import Store, ConfigObject, ConfigControlFrame
 from dcs.config.ui import ui_create
 
 
 class ExampleSubFrame(ConfigControlFrame):
-    pass
+    wdg_id: ttk.Spinbox
 
 
 @ui_create
@@ -25,6 +26,10 @@
 
     def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
         frm = ExampleSubFrame(parent)
+        frm.pack()
+        ttk.Label(frm, text="Id:").grid(row=0, column=0)
+        frm.wdg_id = self.ui_create_id(frm)
+        frm.wdg_id.grid(row=0, column=1)
         return frm
 
 
@@ -73,3 +78,49 @@
 
         cfg: ExampleConfig = store.get_config(ExampleConfig)
         self.assertIn('ui_create_id', dir(cfg))
+
+
+class UIInteractiveTest(unittest.TestCase):
+    def setUp(self) -> None:
+        self.root = Tk()
+        self.frm = ttk.Frame(self.root, padding=10)
+        self.frm.pack()
+
+        self.store = Store.new('default')
+
+    def _print_cfg(self) -> None:
+        print(repr(self.store.get_config(ExampleConfig)))
+        print(toml.dumps(self.store.dump_obj()))
+        #print(self.store.get_config(ExampleConfig).json())
+
+    def _run_ui(self) -> bool:
+        self.res = False
+
+        def act(ok: bool) -> None:
+            self.root.destroy()
+            self.res = ok
+
+        self.ok_btn = ttk.Button(self.root, text='Test OK', command=lambda: act(True))
+        self.ok_btn.pack()
+        self.ok_fail = ttk.Button(self.root, text='Test Fail', command=lambda: act(False))
+        self.ok_fail.pack()
+
+        self.root.mainloop()
+
+        return self.res
+
+    def test_int_widget(self):
+        cfg = self.store.get_config(ExampleConfig)
+        wdg = cfg.ui_create_id(self.frm)
+        wdg.pack()
+        wdg.listen_change(lambda x, y, z: self._print_cfg())
+
+        self.assertTrue(self._run_ui())
+
+    def test_float_widget(self):
+        cfg = self.store.get_config(ExampleConfig)
+        wdg = cfg.ui_create_flt(self.frm)
+        wdg.pack()
+        wdg.listen_change(lambda x, y, z: self._print_cfg())
+
+        self.assertTrue(self._run_ui())