tklr-dgraham 0.0.0rc11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tklr-dgraham might be problematic. Click here for more details.
- tklr/__init__.py +0 -0
- tklr/cli/main.py +253 -0
- tklr/cli/migrate_etm_to_tklr.py +764 -0
- tklr/common.py +1296 -0
- tklr/controller.py +2602 -0
- tklr/item.py +3765 -0
- tklr/list_colors.py +234 -0
- tklr/model.py +3973 -0
- tklr/shared.py +654 -0
- tklr/sounds/alert.mp3 +0 -0
- tklr/tklr_env.py +461 -0
- tklr/use_system.py +64 -0
- tklr/versioning.py +21 -0
- tklr/view.py +2912 -0
- tklr/view_agenda.py +236 -0
- tklr/view_textual.css +296 -0
- tklr_dgraham-0.0.0rc11.dist-info/METADATA +699 -0
- tklr_dgraham-0.0.0rc11.dist-info/RECORD +21 -0
- tklr_dgraham-0.0.0rc11.dist-info/WHEEL +5 -0
- tklr_dgraham-0.0.0rc11.dist-info/entry_points.txt +2 -0
- tklr_dgraham-0.0.0rc11.dist-info/top_level.txt +1 -0
tklr/view_agenda.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from itertools import product
|
|
4
|
+
from string import ascii_lowercase
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
import readchar
|
|
7
|
+
from readchar import key
|
|
8
|
+
import copy
|
|
9
|
+
from typing import List, Tuple
|
|
10
|
+
|
|
11
|
+
from rich.style import Style
|
|
12
|
+
from colorsys import rgb_to_hls
|
|
13
|
+
from tklr.tklr_env import TklrEnvironment
|
|
14
|
+
|
|
15
|
+
# from tklr.model import UrgencyCalulator
|
|
16
|
+
from .shared import log_msg, display_messages
|
|
17
|
+
|
|
18
|
+
# env = TklrEnvironment()
|
|
19
|
+
|
|
20
|
+
# urgency = env.config.urgency
|
|
21
|
+
|
|
22
|
+
HIGHLIGHT = "#6495ED"
|
|
23
|
+
# name_style = Style(color=hex_val)
|
|
24
|
+
# HIGHLIGHT_STYLE = Style(color=get_contrasting_text_color(HIGHLIGHT), bgcolor=HIGHLIGHT)
|
|
25
|
+
HEADER = "#FFF8DC"
|
|
26
|
+
TASK = "#87CEFA"
|
|
27
|
+
EVENT = "#32CD32"
|
|
28
|
+
BEGIN = "#FFD700"
|
|
29
|
+
DRAFT = "#FFA07A"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_agenda_view(controller):
|
|
33
|
+
now = datetime.now()
|
|
34
|
+
console = Console()
|
|
35
|
+
width, height = console.size
|
|
36
|
+
max_lines_per_page = (height - 1) // 2 - 2 # split screen assumption
|
|
37
|
+
|
|
38
|
+
# Get events and tasks from controller
|
|
39
|
+
grouped_events = controller.get_agenda_events() # already grouped and labeled
|
|
40
|
+
tasks = controller.get_agenda_tasks()
|
|
41
|
+
|
|
42
|
+
event_pages = paginate_events_by_line_count(
|
|
43
|
+
grouped_events, max_lines_per_page, today=now.date()
|
|
44
|
+
)
|
|
45
|
+
tagged_event_pages = tag_paginated_events(event_pages)
|
|
46
|
+
urgency_pages = paginate_urgency_tasks(tasks, per_page=max_lines_per_page)
|
|
47
|
+
|
|
48
|
+
agenda_navigation_loop(tagged_event_pages, urgency_pages)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def generate_tags():
|
|
52
|
+
for length in range(1, 3): # a–z, aa–zz
|
|
53
|
+
for combo in product(ascii_lowercase, repeat=length):
|
|
54
|
+
yield "".join(combo)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def format_day_header(date, today):
|
|
58
|
+
dtstr = date.strftime("%a %b %-d")
|
|
59
|
+
tomorrow = today + timedelta(days=1)
|
|
60
|
+
if date == today:
|
|
61
|
+
label = f"[{HEADER}][not bold]{dtstr}[/not bold] (Today)[/{HEADER}]"
|
|
62
|
+
elif date == tomorrow:
|
|
63
|
+
label = f"[{HEADER}][not bold]{dtstr}[/not bold] (Tomorrow)[/{HEADER}]"
|
|
64
|
+
else:
|
|
65
|
+
label = f"[{HEADER}][not bold]{dtstr}[/not bold][/{HEADER}]"
|
|
66
|
+
return label
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def paginate_events_by_line_count(events_by_date, max_lines_per_page, today):
|
|
70
|
+
from copy import deepcopy
|
|
71
|
+
|
|
72
|
+
def format_event_line(label, subject):
|
|
73
|
+
return f"[not bold]{label}[/not bold] {subject}" if label.strip() else subject
|
|
74
|
+
|
|
75
|
+
def calculate_padding(lines_used):
|
|
76
|
+
log_msg(f"{max_lines_per_page = }, {lines_used = }")
|
|
77
|
+
return max(0, max_lines_per_page - lines_used)
|
|
78
|
+
|
|
79
|
+
grouped = deepcopy(events_by_date)
|
|
80
|
+
sorted_dates = sorted(grouped.keys())
|
|
81
|
+
|
|
82
|
+
pages = []
|
|
83
|
+
current_page = []
|
|
84
|
+
current_line_count = 0
|
|
85
|
+
i = 0
|
|
86
|
+
carryover = None
|
|
87
|
+
|
|
88
|
+
while i < len(sorted_dates) or carryover:
|
|
89
|
+
if carryover:
|
|
90
|
+
date, events = carryover
|
|
91
|
+
header = f"{format_day_header(date, today)} - continued"
|
|
92
|
+
else:
|
|
93
|
+
date = sorted_dates[i]
|
|
94
|
+
events = grouped[date]
|
|
95
|
+
header = format_day_header(date, today)
|
|
96
|
+
|
|
97
|
+
lines = []
|
|
98
|
+
continued = False
|
|
99
|
+
available_lines = max_lines_per_page
|
|
100
|
+
|
|
101
|
+
if len(events) > available_lines:
|
|
102
|
+
# Avoid showing lonely header
|
|
103
|
+
if available_lines < 2:
|
|
104
|
+
if current_page:
|
|
105
|
+
# Pad if needed
|
|
106
|
+
pad = calculate_padding(current_line_count)
|
|
107
|
+
if pad:
|
|
108
|
+
current_page.append(("", [""] * pad, False))
|
|
109
|
+
pages.append(current_page)
|
|
110
|
+
current_page = []
|
|
111
|
+
current_line_count = 0
|
|
112
|
+
carryover = (date, events)
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
visible = events[: available_lines - 1]
|
|
116
|
+
remaining = events[available_lines - 1 :]
|
|
117
|
+
lines = [format_event_line(label, subject) for label, subject, _ in visible]
|
|
118
|
+
lines.append("\u21aa [dim]continues on next page[/dim]")
|
|
119
|
+
continued = True
|
|
120
|
+
carryover = (date, remaining)
|
|
121
|
+
else:
|
|
122
|
+
lines = [format_event_line(label, subject) for label, subject, _ in events]
|
|
123
|
+
carryover = None
|
|
124
|
+
continued = False
|
|
125
|
+
i += 1
|
|
126
|
+
|
|
127
|
+
current_page.append((header, lines, continued))
|
|
128
|
+
current_line_count += len(lines) + 1 # +1 for header
|
|
129
|
+
|
|
130
|
+
if current_line_count >= max_lines_per_page:
|
|
131
|
+
pages.append(current_page)
|
|
132
|
+
current_page = []
|
|
133
|
+
current_line_count = 0
|
|
134
|
+
|
|
135
|
+
# Final page padding
|
|
136
|
+
if current_page:
|
|
137
|
+
current_line_count = sum(len(lines) + 1 for _, lines, _ in current_page)
|
|
138
|
+
pad = calculate_padding(current_line_count)
|
|
139
|
+
if pad:
|
|
140
|
+
current_page.append(("", [""] * pad, False))
|
|
141
|
+
pages.append(current_page)
|
|
142
|
+
|
|
143
|
+
return pages
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def tag_paginated_events(pages):
|
|
147
|
+
tagged = []
|
|
148
|
+
for page in pages:
|
|
149
|
+
tag_gen = generate_tags()
|
|
150
|
+
tagged_page = []
|
|
151
|
+
for header, events, continued in page:
|
|
152
|
+
tagged_events = []
|
|
153
|
+
for line in events:
|
|
154
|
+
if line.startswith("\u21aa") or not line.strip():
|
|
155
|
+
tag = ""
|
|
156
|
+
else:
|
|
157
|
+
tag = next(tag_gen)
|
|
158
|
+
tagged_events.append((tag, line))
|
|
159
|
+
hdr = header
|
|
160
|
+
tagged_page.append((hdr, tagged_events))
|
|
161
|
+
tagged.append(tagged_page)
|
|
162
|
+
return tagged
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def paginate_urgency_tasks(tasks, per_page=10):
|
|
166
|
+
pages = []
|
|
167
|
+
current_page = []
|
|
168
|
+
tag_gen = generate_tags()
|
|
169
|
+
for i, (urgency, color, subject, id, job) in enumerate(
|
|
170
|
+
tasks
|
|
171
|
+
): # (urgency, subject, record_id, job_id)
|
|
172
|
+
if i % per_page == 0 and current_page:
|
|
173
|
+
pages.append(current_page)
|
|
174
|
+
current_page = []
|
|
175
|
+
tag_gen = generate_tags()
|
|
176
|
+
tag = next(tag_gen)
|
|
177
|
+
current_page.append((tag, urgency, color, subject, id, job))
|
|
178
|
+
if current_page:
|
|
179
|
+
pages.append(current_page)
|
|
180
|
+
return pages
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def agenda_navigation_loop(event_pages, task_pages):
|
|
184
|
+
console = Console()
|
|
185
|
+
total_event_pages = len(event_pages)
|
|
186
|
+
total_task_pages = len(task_pages)
|
|
187
|
+
event_page = 0
|
|
188
|
+
task_page = 0
|
|
189
|
+
active_pane = "events"
|
|
190
|
+
|
|
191
|
+
while True:
|
|
192
|
+
console.clear()
|
|
193
|
+
event_title = f" Events (Page {event_page + 1} of {total_event_pages}) "
|
|
194
|
+
task_title = f" Tasks (Page {task_page + 1} of {total_task_pages}) "
|
|
195
|
+
|
|
196
|
+
console.rule(
|
|
197
|
+
f"[bold black on {HIGHLIGHT}]{event_title}[/]"
|
|
198
|
+
if active_pane == "events"
|
|
199
|
+
else f"[{HEADER}]{event_title}[/]"
|
|
200
|
+
)
|
|
201
|
+
for header, events in event_pages[event_page]:
|
|
202
|
+
console.print(f"[{HEADER}]{header}[/{HEADER}]")
|
|
203
|
+
for tag, line in events:
|
|
204
|
+
console.print(
|
|
205
|
+
f" [dim]{tag}[/dim] [{EVENT}]{line}[/{EVENT}]"
|
|
206
|
+
if tag
|
|
207
|
+
else f" {line}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
console.rule(
|
|
211
|
+
f"[bold black on {HIGHLIGHT}]{task_title}[/]"
|
|
212
|
+
if active_pane == "tasks"
|
|
213
|
+
else f"[{HEADER}]{task_title}[/]"
|
|
214
|
+
)
|
|
215
|
+
for tag, urgency, color, subject, id, job in task_pages[task_page]:
|
|
216
|
+
console.print(
|
|
217
|
+
f" [dim]{tag}[/dim] [not bold][{color}]{str(round(urgency * 100)):>2}[/{color}] [{TASK}]{subject} [dim]{id} {job if job else ''}[/dim][/{TASK}][/not bold]"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
console.print("\n[dim]←/→ switch page; ↑/↓ switch pane; Q to quit[/dim]")
|
|
221
|
+
|
|
222
|
+
keypress = readchar.readkey()
|
|
223
|
+
if keypress == "Q":
|
|
224
|
+
break
|
|
225
|
+
elif keypress == key.UP or keypress == key.DOWN:
|
|
226
|
+
active_pane = "events" if active_pane == "tasks" else "tasks"
|
|
227
|
+
elif keypress == key.RIGHT:
|
|
228
|
+
if active_pane == "events" and event_page < total_event_pages - 1:
|
|
229
|
+
event_page += 1
|
|
230
|
+
elif active_pane == "tasks" and task_page < total_task_pages - 1:
|
|
231
|
+
task_page += 1
|
|
232
|
+
elif keypress == key.LEFT:
|
|
233
|
+
if active_pane == "events" and event_page > 0:
|
|
234
|
+
event_page -= 1
|
|
235
|
+
elif active_pane == "tasks" and task_page > 0:
|
|
236
|
+
task_page -= 1
|
tklr/view_textual.css
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/* ── Screen ───────────────────────────────────────── */
|
|
2
|
+
Screen {
|
|
3
|
+
layout: vertical;
|
|
4
|
+
background: #373737 100%;
|
|
5
|
+
opacity: 100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Screen, */
|
|
9
|
+
/* SafeScreen, */
|
|
10
|
+
/* ScrollableList, */
|
|
11
|
+
/* ListWithDetails { */
|
|
12
|
+
/* layout: vertical; */
|
|
13
|
+
/* background: #373737; */
|
|
14
|
+
/* } */
|
|
15
|
+
|
|
16
|
+
/* ── Titles ───────────────────────────────────────── */
|
|
17
|
+
#table_title,
|
|
18
|
+
#week_title,
|
|
19
|
+
#scroll_title,
|
|
20
|
+
#details_title,
|
|
21
|
+
#list_title,
|
|
22
|
+
#events_title,
|
|
23
|
+
#tasks_title,
|
|
24
|
+
#dt_title,
|
|
25
|
+
#ed_title,
|
|
26
|
+
#bins_title {
|
|
27
|
+
color: white;
|
|
28
|
+
border-bottom: solid #7f7f7f;
|
|
29
|
+
text-style: bold;
|
|
30
|
+
text-align: center;
|
|
31
|
+
padding: 1 1 0 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ── ScrollView defaults (plain) ──────────────────── */
|
|
35
|
+
ScrollView {
|
|
36
|
+
padding: 0;
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Hidden utility */
|
|
41
|
+
.hidden {
|
|
42
|
+
display: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#custom_footer {
|
|
46
|
+
color: white;
|
|
47
|
+
text-style: bold;
|
|
48
|
+
border-top: #7f7f7f;
|
|
49
|
+
padding: 0 1 1 1;
|
|
50
|
+
}
|
|
51
|
+
/* ── Details Pane ───────────────────────────── */
|
|
52
|
+
#details-pane {
|
|
53
|
+
dock: bottom;
|
|
54
|
+
layout: vertical; /* header then body */
|
|
55
|
+
border-top: heavy #7f7f7f; /* optional separator */
|
|
56
|
+
height: 14; /* fixed: 2 rows for header + 12 for body */
|
|
57
|
+
padding: 0 1 1 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#details-pane.hidden {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#details-title {
|
|
65
|
+
width: 1fr;
|
|
66
|
+
max-width: 1fr;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
text-overflow: ellipsis;
|
|
69
|
+
padding: 0 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#details-body {
|
|
73
|
+
height: 1fr; /* fills remaining space in the pane */
|
|
74
|
+
border: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#details-body-content {
|
|
78
|
+
border: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* (Optional) Help panel styles you already had */
|
|
82
|
+
#help_panel {
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: 100%;
|
|
85
|
+
border: solid #7f7f7f;
|
|
86
|
+
}
|
|
87
|
+
#help_layout {
|
|
88
|
+
height: 1fr;
|
|
89
|
+
}
|
|
90
|
+
#help_scroll {
|
|
91
|
+
height: 1fr;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.dim-rule {
|
|
95
|
+
color: #7f7f7f;
|
|
96
|
+
height: 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ---- DatetimePrompt Layout ---- */
|
|
100
|
+
|
|
101
|
+
/* Padding Supply 1, 2 or 4 integers separated by a space */
|
|
102
|
+
/* e.g. padding: 1; */
|
|
103
|
+
/* e.g. padding: 1 2; # Vertical, horizontal */
|
|
104
|
+
/* e.g. padding: 1 2 3 4; # Top, right, bottom, left */
|
|
105
|
+
|
|
106
|
+
/* Title bar FOR ALL TITLES */
|
|
107
|
+
.title-class {
|
|
108
|
+
text-align: center;
|
|
109
|
+
color: white;
|
|
110
|
+
text-style: bold;
|
|
111
|
+
border-bottom: solid #7f7f7f;
|
|
112
|
+
/* background: #373737; */
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Horizontal rule */
|
|
116
|
+
.dim-rule {
|
|
117
|
+
color: #555555;
|
|
118
|
+
background: #373737;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Live feedback (“→ parsed datetime”) */
|
|
122
|
+
#dt_feedback {
|
|
123
|
+
color: limegreen;
|
|
124
|
+
padding: 0 1;
|
|
125
|
+
}
|
|
126
|
+
/* Message block just under the title */
|
|
127
|
+
#dt_message {
|
|
128
|
+
color: #cccccc; /* soft grey */
|
|
129
|
+
background: #373737;
|
|
130
|
+
padding: 0 1 1 1;
|
|
131
|
+
/* text-align: center; */
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#dt_entry:focus {
|
|
135
|
+
border: solid #7f7f7f;
|
|
136
|
+
/* background: #373737; */
|
|
137
|
+
background: #2e2e2e;
|
|
138
|
+
color: white;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Fixed universal instructions */
|
|
142
|
+
#dt_instructions {
|
|
143
|
+
color: #cccccc;
|
|
144
|
+
background: #373737;
|
|
145
|
+
padding: 0 1;
|
|
146
|
+
text-align: center;
|
|
147
|
+
/* text-style: dim; */
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#ed_prompt {
|
|
151
|
+
layout: vertical;
|
|
152
|
+
padding: 0 1;
|
|
153
|
+
}
|
|
154
|
+
/* Message block just under the title */
|
|
155
|
+
|
|
156
|
+
#ed_instructions {
|
|
157
|
+
color: #cccccc;
|
|
158
|
+
background: #373737;
|
|
159
|
+
padding: 0 1;
|
|
160
|
+
text-align: center;
|
|
161
|
+
/* text-style: dim; */
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#ed_message {
|
|
165
|
+
color: #cccccc; /* soft grey */
|
|
166
|
+
background: #373737;
|
|
167
|
+
padding: 0 1;
|
|
168
|
+
/* text-align: center; */
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#ed_entry:focus {
|
|
172
|
+
border: solid #7f7f7f;
|
|
173
|
+
/* background: #373737; */
|
|
174
|
+
background: #3a3a3a;
|
|
175
|
+
/* background: #2e2e2e; */
|
|
176
|
+
color: white;
|
|
177
|
+
height: auto;
|
|
178
|
+
padding: 0 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#ed_feedback {
|
|
182
|
+
height: auto;
|
|
183
|
+
color: limegreen;
|
|
184
|
+
padding: 0 1 0 1;
|
|
185
|
+
}
|
|
186
|
+
/* Container that holds a main list + details list */
|
|
187
|
+
ListWithDetails {
|
|
188
|
+
layout: vertical;
|
|
189
|
+
/* background: #373737; */
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Main list fills available space */
|
|
193
|
+
ListWithDetails > #main-list {
|
|
194
|
+
/* background: #373737; */
|
|
195
|
+
height: 1fr;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Details list appears at bottom when shown and scrolls if long */
|
|
199
|
+
ListWithDetails > #details-list {
|
|
200
|
+
/* background: #373737; */
|
|
201
|
+
height: auto;
|
|
202
|
+
max-height: 14; /* tweak to taste */
|
|
203
|
+
border-top: solid #7f7f7f; /* optional separator */
|
|
204
|
+
/* border-bottom: solid #7f7f7f; */
|
|
205
|
+
/* padding-left: 1; */
|
|
206
|
+
/* border: none; */
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
ListWithDetails > #details_text {
|
|
210
|
+
/* background: #373737; */
|
|
211
|
+
color: yellow;
|
|
212
|
+
/* height: auto; */
|
|
213
|
+
/* max-height: 14; */
|
|
214
|
+
/* border-top: solid #7f7f7f; */
|
|
215
|
+
/* border-bottom: solid #7f7f7f; */
|
|
216
|
+
/* padding-left: 1; */
|
|
217
|
+
/* border: none; */
|
|
218
|
+
padding: 1 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
ListWithDetails > #details-list.hidden {
|
|
222
|
+
display: none;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.busy-bar {
|
|
226
|
+
text-style: bold;
|
|
227
|
+
text-align: center;
|
|
228
|
+
/* padding: 0 0 1 0; */
|
|
229
|
+
height: auto;
|
|
230
|
+
/* border: none; */
|
|
231
|
+
border-bottom: #7f7f7f; /* optional separator */
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* ── BinHierarchyScreen ───────────────────────────── */
|
|
235
|
+
|
|
236
|
+
/* Make the tree fill the vertical space between Header and Footer */
|
|
237
|
+
#bins-tree {
|
|
238
|
+
height: 1fr; /* grow to fill */
|
|
239
|
+
margin: 0;
|
|
240
|
+
padding: 0 1; /* match your other lists */
|
|
241
|
+
/* border: solid #7f7f7f; */
|
|
242
|
+
background: #373737; /* same dark base */
|
|
243
|
+
color: #e0e0e0; /* legible on dark */
|
|
244
|
+
overflow: auto; /* scroll if tall */
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* Stronger focus ring to mirror inputs’ focus state */
|
|
248
|
+
#bins-tree:focus {
|
|
249
|
+
/* border: heavy #7f7f7f; */
|
|
250
|
+
background: #2e2e2e; /* slight focus darken like #dt_entry / #ed_entry */
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Optional: tighten header/footer to your house style */
|
|
254
|
+
BinHierarchyScreen > Header,
|
|
255
|
+
BinHierarchyScreen > Footer {
|
|
256
|
+
background: #373737;
|
|
257
|
+
color: white;
|
|
258
|
+
border: none;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* ── Tree niceties (Textual’s internal parts) ───────
|
|
262
|
+
These selectors are supported by Textual’s Tree widget.
|
|
263
|
+
If you’re on a very old Textual, you can safely delete them. */
|
|
264
|
+
|
|
265
|
+
/* Guides (│ ├ └) */
|
|
266
|
+
#bins-tree .tree--guides {
|
|
267
|
+
color: #7f7f7f;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* The row your cursor is on */
|
|
271
|
+
#bins-tree .tree--cursor {
|
|
272
|
+
background: #2e2e2e;
|
|
273
|
+
color: white;
|
|
274
|
+
text-style: bold;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Labels (node text) */
|
|
278
|
+
#bins-tree .tree--label {
|
|
279
|
+
color: #e0e0e0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* Expanded node labels a touch bolder for hierarchy pop */
|
|
283
|
+
#bins-tree .tree--label.-expanded {
|
|
284
|
+
text-style: bold;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Dim disabled nodes (if you ever mark any) */
|
|
288
|
+
#bins-tree .-disabled .tree--label {
|
|
289
|
+
color: #7f7f7f;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Scrollbar to match the rest of the app (works on recent Textual) */
|
|
293
|
+
#bins-tree {
|
|
294
|
+
scrollbar-gutter: stable;
|
|
295
|
+
scrollbar-color: #7f7f7f #373737; /* thumb, track */
|
|
296
|
+
}
|