Skip to content

Commit ddddbc9

Browse files
Merge pull request #18 from koenvervloesem/ui_improvements
UI improvements
2 parents 578d71b + c153888 commit ddddbc9

File tree

4 files changed

+57
-28
lines changed

4 files changed

+57
-28
lines changed

docs/_static/screenshot.png

20.7 KB
Loading

docs/usage.rst

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ The footer shows shortcut keys that are recognized by the program:
5151

5252
* Q: Quit the program
5353
* F: Filter the devices that are shown
54-
* A: Choose which advertisement data to show
55-
* S: Start or stop scan
54+
* S: Change settings
55+
* T: Start or stop scan
56+
* C: Clear all advertisements
5657

5758
Filtering devices
5859
-----------------
@@ -61,17 +62,22 @@ If you press the **F** key, an input widget appears where you can start typing a
6162

6263
When you click outside the filter widget or press **Tab** to bring the focus to the next visible widget, the filter widget disappears, but the filter is still applied to limit the shown advertisements. Just press **F** again to change the filter, for instance by removing the filter with **Backspace** or changing the Bluetooth address part to filter on.
6364

64-
Showing and hiding advertisement data
65-
-------------------------------------
65+
Changing settings
66+
-----------------
6667

67-
If you press the **A** key, you can choose which advertising data types are shown in the table. By default all data types are shown, but you can enable or disable each of them individually by clicking on the checkbox or focusing it with **Tab** and then press **Enter** or **Space** to toggle it.
68+
If you press the **S** key, you can choose which advertising data types are shown in the table. By default all data types are shown, but you can enable or disable each of them individually by clicking on the checkbox or focusing it with **Tab** and then press **Enter** or **Space** to toggle it. You can also change some other settings, such as autoscrolling.
6869

6970
Starting and stopping the scan
7071
------------------------------
7172

72-
If you press the **S** key, you stop the scan if it's running and you start the scan if it's stopped.
73+
If you press the **T** key, you stop the scan if it's running and you start the scan if it's stopped.
74+
75+
When the scan is running and autoscrolling is enabled in the settings, the program continuously scrolls the table with advertisements so you are always seeing the most recent results. When the scan isn't running or autoscrolling is disabled, you can scroll through the history of received advertisements with the scroll wheel, by dragging the scroll bar, or by pressing **PgUp**, **PgDown** or the arrow keys up and down when the table widget is focused.
76+
77+
Clearing all advertisements
78+
---------------------------
7379

74-
When the scan is running, the program continuously scrolls the table with advertisements so you are always seeing the most recent results. When the scan isn't running, you can scroll through the history of received advertisements with the scroll wheel, by dragging the scroll bar, or by pressing **PgUp**, **PgDown** or the arrow keys up and down when the table widget is focused.
80+
If you press the **C** key, the program clears all received advertisements. The table is filled again with newly received advertisements.
7581

7682
Quitting the program
7783
--------------------

src/humble_explorer/app.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from bleak.backends.bluezdbus.scanner import BlueZScannerArgs
1717

1818
from humble_explorer.renderables import DeviceAddress, RichAdvertisement, Time
19-
from humble_explorer.widgets import FilterWidget, ShowDataWidget
19+
from humble_explorer.widgets import FilterWidget, SettingsWidget
2020

2121
from . import __version__
2222

@@ -31,12 +31,12 @@ class BLEScannerApp(App[None]):
3131
"""A Textual app to scan for Bluetooth Low Energy advertisements."""
3232

3333
CSS_PATH = "app.css"
34-
TITLE = f"HumBLE Explorer {__version__}"
3534
BINDINGS = [
3635
("q", "quit", "Quit"),
3736
("f", "toggle_filter", "Filter"),
38-
("a", "toggle_data", "Data"),
39-
("s", "toggle_scan", "Toggle scan"),
37+
("s", "toggle_settings", "Settings"),
38+
("t", "toggle_scan", "Toggle scan"),
39+
("c", "clear_advertisements", "Clear"),
4040
]
4141

4242
address_filter = reactive("")
@@ -70,10 +70,14 @@ def __init__(self, cli_args: Namespace):
7070

7171
super().__init__()
7272

73-
def action_toggle_data(self) -> None:
74-
"""Enable or disable data widget."""
75-
data_widget = self.query_one(ShowDataWidget)
76-
data_widget.display = not data_widget.display
73+
def set_title(self, scanning_description):
74+
"""Set the title of the app with a description of the scanning status."""
75+
self.title = f"HumBLE Explorer {__version__} ({scanning_description})"
76+
77+
def action_toggle_settings(self) -> None:
78+
"""Enable or disable settings widget."""
79+
settings_widget = self.query_one(SettingsWidget)
80+
settings_widget.display = not settings_widget.display
7781

7882
def action_toggle_filter(self) -> None:
7983
"""Enable or disable filter input widget."""
@@ -89,11 +93,16 @@ async def action_toggle_scan(self) -> None:
8993
else:
9094
await self.start_scan()
9195

96+
def action_clear_advertisements(self) -> None:
97+
"""Clear the list of received advertisements."""
98+
self.advertisements = []
99+
self.query_one(DataTable).clear()
100+
92101
def compose(self) -> ComposeResult:
93102
"""Create child widgets for the app."""
94103
yield Header()
95104
yield Footer()
96-
yield ShowDataWidget(id="sidebar")
105+
yield SettingsWidget(id="sidebar")
97106
yield FilterWidget(placeholder="address=")
98107
yield DataTable(zebra_stripes=True)
99108

@@ -141,7 +150,8 @@ def on_checkbox_changed(self, message: Checkbox.Changed) -> None:
141150
"""Show or hide advertisement data depending on the state of
142151
the checkboxes.
143152
"""
144-
self.recreate_table()
153+
if "view" in message.input.classes:
154+
self.recreate_table()
145155

146156
def on_input_changed(self, message: Input.Changed) -> None:
147157
"""Filter advertisements with user-supplied filter."""
@@ -168,6 +178,11 @@ def recreate_table(self):
168178
RichAdvertisement(advertisement[2], self.show_data_config()),
169179
)
170180

181+
def scroll_if_autoscroll(self):
182+
"""Scroll to the end if autoscroll is enabled."""
183+
if self.query_one("#autoscroll").value:
184+
self.query_one(DataTable).scroll_end(animate=False)
185+
171186
def add_advertisement_to_table(
172187
self, table, now, device_address, rich_advertisement
173188
):
@@ -179,17 +194,19 @@ def add_advertisement_to_table(
179194
rich_advertisement,
180195
height=rich_advertisement.height(),
181196
)
182-
table.scroll_end(animate=False)
197+
self.scroll_if_autoscroll()
183198

184199
async def start_scan(self) -> None:
185200
"""Start BLE scan."""
186201
self.scanning = True
202+
self.set_title("Scanning")
187203
await self.scanner.start()
188204

189205
async def stop_scan(self) -> None:
190206
"""Stop BLE scan."""
191207
self.scanning = False
208+
self.set_title("Stopped")
192209
await self.scanner.stop()
193210
table = self.query_one(DataTable)
194211
table.add_row(Time(datetime.now(), style=_PAUSE_STYLE))
195-
table.scroll_end(animate=False)
212+
self.scroll_if_autoscroll()

src/humble_explorer/widgets.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,44 +19,50 @@ def on_blur(self, message: Input.on_blur) -> None:
1919
self.display = False
2020

2121

22-
class ShowDataWidget(Static):
23-
"""A Textual widget to let the user choose what advertisement data to show."""
22+
class SettingsWidget(Static):
23+
"""A Textual widget to let the user choose settings."""
2424

2525
def __init__(self, *args, **kwargs):
2626
super().__init__(*args, **kwargs)
2727
self.display = False
2828

2929
def compose(self) -> ComposeResult:
3030
"""Show checkboxes."""
31-
yield Static("[b]Show data\n")
31+
yield Static("[b]Show data types[/b]\n")
3232
yield Horizontal(
3333
Static("Local name ", classes="label"),
34-
Checkbox(value=True, id="local_name"),
34+
Checkbox(value=True, id="local_name", classes="view"),
3535
classes="container",
3636
)
3737
yield Horizontal(
3838
Static("RSSI ", classes="label"),
39-
Checkbox(value=True, id="rssi"),
39+
Checkbox(value=True, id="rssi", classes="view"),
4040
classes="container",
4141
)
4242
yield Horizontal(
4343
Static("TX power ", classes="label"),
44-
Checkbox(value=True, id="tx_power"),
44+
Checkbox(value=True, id="tx_power", classes="view"),
4545
classes="container",
4646
)
4747
yield Horizontal(
4848
Static("Manufacturer data", classes="label"),
49-
Checkbox(value=True, id="manufacturer_data"),
49+
Checkbox(value=True, id="manufacturer_data", classes="view"),
5050
classes="container",
5151
)
5252
yield Horizontal(
5353
Static("Service data ", classes="label"),
54-
Checkbox(value=True, id="service_data"),
54+
Checkbox(value=True, id="service_data", classes="view"),
5555
classes="container",
5656
)
5757
yield Horizontal(
5858
Static("Service UUIDs ", classes="label"),
59-
Checkbox(value=True, id="service_uuids"),
59+
Checkbox(value=True, id="service_uuids", classes="view"),
60+
classes="container",
61+
)
62+
yield Static("\n[b]Other settings[/b]\n")
63+
yield Horizontal(
64+
Static("Auto-scroll ", classes="label"),
65+
Checkbox(value=True, id="autoscroll", classes="view"),
6066
classes="container",
6167
)
6268

0 commit comments

Comments
 (0)