@brimveyn/aimux 1.23.7 → 1.24.0
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.
- package/package.json +2 -2
- package/src/input/keymap/key-chord.ts +10 -0
- package/src/input/keymap/key-format.ts +14 -1
- package/src/input/modes/handlers/shared.ts +48 -0
- package/src/state/actions.ts +2 -1
- package/src/state/reducers/modal-state.ts +24 -28
- package/src/state/text-cursor.ts +117 -0
- package/src/ui/breaking-update-screen.tsx +7 -1
- package/src/ui/components/layout/bar-footer.tsx +3 -5
- package/src/ui/components/layout/bar.tsx +41 -16
- package/src/ui/components/layout/sidebar/project-list.tsx +6 -16
- package/src/ui/components/layout/sidebar/workspace-row.tsx +6 -9
- package/src/ui/components/layout/status-bar.tsx +9 -3
- package/src/ui/components/layout/terminal-pane.tsx +18 -16
- package/src/ui/components/modals/app/help-modal.tsx +20 -15
- package/src/ui/components/modals/app/update-available-modal.tsx +3 -1
- package/src/ui/components/modals/git/git-commit-modal.tsx +2 -2
- package/src/ui/components/modals/projects/create-project-modal.tsx +9 -3
- package/src/ui/components/modals/projects/project-picker-modal.tsx +6 -6
- package/src/ui/components/modals/settings/settings-search-modal.tsx +19 -14
- package/src/ui/components/modals/shared/modal-shell.tsx +12 -2
- package/src/ui/components/modals/shared/picker.tsx +8 -4
- package/src/ui/components/modals/snippets/snippet-picker-modal.tsx +7 -5
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +10 -12
- package/src/ui/components/modals/themes/theme-picker-modal.tsx +14 -10
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +19 -14
- package/src/ui/components/modals/workspace/workspace-move-modal.tsx +4 -2
- package/src/ui/components/overlays/context-menu/context-menu-overlay.tsx +10 -4
- package/src/ui/components/primitives/list-item.tsx +24 -2
- package/src/ui/components/primitives/surface.tsx +4 -1
- package/src/ui/components/settings/row-value.tsx +8 -1
- package/src/ui/components/settings/settings-footer.tsx +3 -2
- package/src/ui/components/settings/settings-row.tsx +3 -0
- package/src/ui/components/settings/settings-search-bar.tsx +4 -6
- package/src/ui/components/settings/settings-view.tsx +25 -21
- package/src/ui/components/stats/stats-view.tsx +4 -2
- package/src/ui/selection-ink.ts +20 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ContextMenuState,
|
|
10
10
|
subscribeContextMenu,
|
|
11
11
|
} from '../../../context-menu/controller'
|
|
12
|
+
import { useSelectionInk } from '../../../selection-ink'
|
|
12
13
|
import { useTheme, useTransparent } from '../../../theme'
|
|
13
14
|
import { fillBorderedBoxInterior } from '../../../transparent-fill'
|
|
14
15
|
|
|
@@ -28,6 +29,7 @@ const MenuItem = memo(function MenuItem({
|
|
|
28
29
|
width: number
|
|
29
30
|
}) {
|
|
30
31
|
const t = useTheme()
|
|
32
|
+
const ink = useSelectionInk()
|
|
31
33
|
const handleMouseOver = useCallback(() => onHover(index), [index, onHover])
|
|
32
34
|
const handleMouseDown = useCallback(
|
|
33
35
|
(e: OtuiMouseEvent) => {
|
|
@@ -45,11 +47,11 @@ const MenuItem = memo(function MenuItem({
|
|
|
45
47
|
flexShrink={0}
|
|
46
48
|
paddingLeft={1}
|
|
47
49
|
paddingRight={1}
|
|
48
|
-
backgroundColor={active ? t.
|
|
50
|
+
backgroundColor={active ? t.primary : undefined}
|
|
49
51
|
onMouseOver={handleMouseOver}
|
|
50
52
|
onMouseDown={handleMouseDown}
|
|
51
53
|
>
|
|
52
|
-
<text fg={active ?
|
|
54
|
+
<text fg={active ? ink : t.textMuted} selectable={false}>
|
|
53
55
|
{label}
|
|
54
56
|
</text>
|
|
55
57
|
</box>
|
|
@@ -131,9 +133,13 @@ export function ContextMenuOverlay() {
|
|
|
131
133
|
left={left}
|
|
132
134
|
width={width}
|
|
133
135
|
flexDirection="column"
|
|
134
|
-
|
|
136
|
+
// backgroundElement, not backgroundPanel: this pops up over the bars as
|
|
137
|
+
// often as over the terminal, and the bars are panel — a panel menu on a
|
|
138
|
+
// panel bar has no edge. Transparent mode keeps the border for the same
|
|
139
|
+
// reason the modal does: there is no background there to stand on.
|
|
140
|
+
border={transparent}
|
|
135
141
|
borderColor={t.border}
|
|
136
|
-
backgroundColor={transparent ? 'transparent' : t.
|
|
142
|
+
backgroundColor={transparent ? 'transparent' : t.backgroundElement}
|
|
137
143
|
renderAfter={transparent ? fillBorderedBoxInterior : undefined}
|
|
138
144
|
onMouseDown={handleMenuMouseDown}
|
|
139
145
|
>
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { memo, type ReactNode, useMemo } from 'react'
|
|
2
2
|
|
|
3
|
+
import { useSelectionInk } from '../../selection-ink'
|
|
3
4
|
import { useTheme } from '../../theme'
|
|
4
5
|
import { Surface } from './surface'
|
|
5
6
|
|
|
6
7
|
export type ListItemDirection = 'row' | 'column'
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* How the selected row is filled. `primary` by default — the accent fill every
|
|
11
|
+
* picker and nav list wants. `elevated` is for a row that carries a colour scale
|
|
12
|
+
* of its own (the settings rows and their gauges): an accent behind those would
|
|
13
|
+
* swallow the very thing the row is there to show.
|
|
14
|
+
*/
|
|
15
|
+
export type ListItemFill = 'primary' | 'elevated'
|
|
16
|
+
|
|
8
17
|
interface ListItemProps {
|
|
9
18
|
active: boolean
|
|
10
19
|
direction?: ListItemDirection
|
|
20
|
+
fill?: ListItemFill
|
|
11
21
|
id?: string
|
|
12
22
|
index?: number
|
|
13
23
|
leading?: ReactNode
|
|
@@ -23,6 +33,7 @@ interface ListItemProps {
|
|
|
23
33
|
export const ListItem = memo(function ListItem({
|
|
24
34
|
active,
|
|
25
35
|
direction = 'column',
|
|
36
|
+
fill = 'primary',
|
|
26
37
|
id,
|
|
27
38
|
index,
|
|
28
39
|
leading,
|
|
@@ -35,7 +46,14 @@ export const ListItem = memo(function ListItem({
|
|
|
35
46
|
trailing,
|
|
36
47
|
}: ListItemProps) {
|
|
37
48
|
const t = useTheme()
|
|
49
|
+
const ink = useSelectionInk()
|
|
38
50
|
const isRow = direction === 'row'
|
|
51
|
+
const filled = active && fill === 'primary'
|
|
52
|
+
// The cursor glyph: the selection ink on a filled row, the accent on a row
|
|
53
|
+
// that only lifts, muted on the rest.
|
|
54
|
+
let markerFg = t.textMuted
|
|
55
|
+
if (filled) markerFg = ink
|
|
56
|
+
else if (active) markerFg = t.primary
|
|
39
57
|
// Build per-item handlers from the stable index-based callbacks so callers can
|
|
40
58
|
// avoid creating fresh closures inside their list `.map()`.
|
|
41
59
|
const handleHover = useMemo<(() => void) | undefined>(() => {
|
|
@@ -51,7 +69,7 @@ export const ListItem = memo(function ListItem({
|
|
|
51
69
|
<box flexDirection="row">
|
|
52
70
|
{isRow ? null : (
|
|
53
71
|
<>
|
|
54
|
-
<text fg={
|
|
72
|
+
<text fg={markerFg}>{active ? '›' : '·'}</text>
|
|
55
73
|
<text> </text>
|
|
56
74
|
</>
|
|
57
75
|
)}
|
|
@@ -66,7 +84,11 @@ export const ListItem = memo(function ListItem({
|
|
|
66
84
|
return (
|
|
67
85
|
<box id={id} onMouseOver={handleHover} onMouseDown={handleClick}>
|
|
68
86
|
{active ? (
|
|
69
|
-
<Surface
|
|
87
|
+
<Surface
|
|
88
|
+
tone={fill === 'primary' ? 'selected' : 'elevated'}
|
|
89
|
+
paddingLeft={isRow ? 2 : 1}
|
|
90
|
+
paddingRight={isRow ? 2 : 1}
|
|
91
|
+
>
|
|
70
92
|
{inner}
|
|
71
93
|
</Surface>
|
|
72
94
|
) : (
|
|
@@ -10,8 +10,11 @@ function toneToToken(tone: SurfaceTone): TuiColorToken {
|
|
|
10
10
|
switch (tone) {
|
|
11
11
|
case 'elevated':
|
|
12
12
|
return 'backgroundElement'
|
|
13
|
+
// Filled with the theme's own accent, not another step of grey: a
|
|
14
|
+
// selection is the one thing on the screen you always need to find without
|
|
15
|
+
// reading. Everything drawn on it uses `useSelectionInk`.
|
|
13
16
|
case 'selected':
|
|
14
|
-
return '
|
|
17
|
+
return 'primary'
|
|
15
18
|
case 'input':
|
|
16
19
|
return 'backgroundElement'
|
|
17
20
|
case 'inputActive':
|
|
@@ -74,9 +74,16 @@ function valueColor(row: SettingRow, value: SettingValue, t: ResolvedTuiTheme):
|
|
|
74
74
|
* list would repaint it at the rate the terminals print.
|
|
75
75
|
*/
|
|
76
76
|
export const RowValue = memo(function RowValue({
|
|
77
|
+
fg,
|
|
77
78
|
maxWidth,
|
|
78
79
|
row,
|
|
79
80
|
}: {
|
|
81
|
+
/**
|
|
82
|
+
* Overrides the value's own colour. A row filled with `primary` has room for
|
|
83
|
+
* one ink, so the search results hand theirs down rather than let a green
|
|
84
|
+
* toggle sit on the accent.
|
|
85
|
+
*/
|
|
86
|
+
fg?: string
|
|
80
87
|
/** Cropped rather than wrapped: a row that grows a line shifts the list. */
|
|
81
88
|
maxWidth?: number
|
|
82
89
|
row: SettingRow
|
|
@@ -86,7 +93,7 @@ export const RowValue = memo(function RowValue({
|
|
|
86
93
|
const value = useAppStore((s) => readRow(row, { state: s, values }))
|
|
87
94
|
const text = formatValue(row, value)
|
|
88
95
|
return (
|
|
89
|
-
<text fg={valueColor(row, value, t)} wrapMode="none">
|
|
96
|
+
<text fg={fg ?? valueColor(row, value, t)} wrapMode="none">
|
|
90
97
|
{maxWidth === undefined ? text : truncate(text, maxWidth)}
|
|
91
98
|
</text>
|
|
92
99
|
)
|
|
@@ -7,7 +7,6 @@ import { useAppStore } from '../../../state/app-store'
|
|
|
7
7
|
import { dispatchGlobal } from '../../../state/dispatch-ref'
|
|
8
8
|
import { useTheme } from '../../theme'
|
|
9
9
|
import { truncate } from '../../truncate'
|
|
10
|
-
import { Rule } from '../stats/shared'
|
|
11
10
|
import { describeValue } from './row-value'
|
|
12
11
|
|
|
13
12
|
/**
|
|
@@ -121,7 +120,9 @@ export const SettingsFooter = memo(function SettingsFooter({
|
|
|
121
120
|
|
|
122
121
|
return (
|
|
123
122
|
<box flexDirection="column" flexShrink={0} paddingLeft={pad} paddingRight={pad}>
|
|
124
|
-
|
|
123
|
+
{/* A blank row, not a rule: the footer is set off from the list by the gap,
|
|
124
|
+
the same way the sidebar's is. */}
|
|
125
|
+
<box height={1} flexShrink={0} />
|
|
125
126
|
<box width={width} flexDirection="row" flexShrink={0}>
|
|
126
127
|
<box flexGrow={1} flexShrink={1}>
|
|
127
128
|
<text fg={t.textMuted} selectable={false} wrapMode="none">
|
|
@@ -119,6 +119,9 @@ export const SettingsRow = memo(function SettingsRow({
|
|
|
119
119
|
<ListItem
|
|
120
120
|
id={`setting-row-${row.id}`}
|
|
121
121
|
active={active}
|
|
122
|
+
// Lifted, not filled: the gauge is drawn in `primary` and the value's
|
|
123
|
+
// colour is its state, both of which an accent fill would swallow.
|
|
124
|
+
fill="elevated"
|
|
122
125
|
index={index}
|
|
123
126
|
onClickIndex={onSelect}
|
|
124
127
|
leading={<text fg={fromConfigFile ? t.warning : t.secondary}>{mark}</text>}
|
|
@@ -22,14 +22,12 @@ export const SettingsSearchBar = memo(function SettingsSearchBar({ width }: { wi
|
|
|
22
22
|
dispatchGlobal({ type: 'open-settings-search' })
|
|
23
23
|
}, [])
|
|
24
24
|
|
|
25
|
-
// Filled,
|
|
26
|
-
//
|
|
27
|
-
//
|
|
25
|
+
// Filled, and the prompt at full strength: it is the one control on a
|
|
26
|
+
// read-mostly screen, and a control drawn like the text around it is a control
|
|
27
|
+
// nobody sees. The fill is enough to say so — the frame it used to wear cost
|
|
28
|
+
// two rows to repeat what the fill already said.
|
|
28
29
|
return (
|
|
29
30
|
<box
|
|
30
|
-
border
|
|
31
|
-
borderStyle="rounded"
|
|
32
|
-
borderColor={t.borderActive}
|
|
33
31
|
backgroundColor={t.backgroundElement}
|
|
34
32
|
width={width}
|
|
35
33
|
flexShrink={0}
|
|
@@ -10,9 +10,10 @@ import { useAppStore } from '../../../state/app-store'
|
|
|
10
10
|
import { clampBarWidth } from '../../../state/bars'
|
|
11
11
|
import { dispatchGlobal, runSideEffectGlobal } from '../../../state/dispatch-ref'
|
|
12
12
|
import { useScrollActiveIntoView } from '../../hooks/use-scroll-active-into-view'
|
|
13
|
+
import { useSelectionInk } from '../../selection-ink'
|
|
13
14
|
import { useTheme } from '../../theme'
|
|
14
15
|
import { ListItem } from '../primitives/list-item'
|
|
15
|
-
import { Muted,
|
|
16
|
+
import { Muted, Section, TileRow } from '../stats/shared'
|
|
16
17
|
import { SettingsFooter } from './settings-footer'
|
|
17
18
|
import { CONFIG_FILE_MARK, SettingsRow, TOUCHED_MARK } from './settings-row'
|
|
18
19
|
import { SettingsSearchBar } from './settings-search-bar'
|
|
@@ -23,6 +24,8 @@ const SETTINGS_GLYPH = '\u{2699}'
|
|
|
23
24
|
|
|
24
25
|
/** One padding column either side, the same inset the stats pages sit in. */
|
|
25
26
|
const PAGE_PAD = 1
|
|
27
|
+
/** Matches the bar's own gutter, so the nav and a bar line up to the column. */
|
|
28
|
+
const NAV_GUTTER = 2
|
|
26
29
|
|
|
27
30
|
/** A section and the rows it owns, each keeping the index the cursor holds. */
|
|
28
31
|
interface Group {
|
|
@@ -71,6 +74,7 @@ function groupBySection(hits: SettingSearchHit[]): Group[] {
|
|
|
71
74
|
*/
|
|
72
75
|
export const SettingsView = memo(function SettingsView() {
|
|
73
76
|
const t = useTheme()
|
|
77
|
+
const ink = useSelectionInk()
|
|
74
78
|
// The cursor is all this component needs from the app state. Rows read their
|
|
75
79
|
// own values, so nothing here re-renders at the rate the terminals print.
|
|
76
80
|
const rowIndex = useAppStore((s) => s.settings.rowIndex)
|
|
@@ -146,14 +150,21 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
146
150
|
[hits.length, touchedCount, configCount]
|
|
147
151
|
)
|
|
148
152
|
|
|
149
|
-
//
|
|
153
|
+
// The same gutter the bar draws between itself and the terminal, so the two
|
|
150
154
|
// views line up to the column.
|
|
151
|
-
const navContentWidth = Math.max(1, navWidth -
|
|
155
|
+
const navContentWidth = Math.max(1, navWidth - NAV_GUTTER)
|
|
152
156
|
const usable = Math.max(24, dimensions.width - navWidth - 2 - PAGE_PAD * 2)
|
|
153
157
|
|
|
154
158
|
return (
|
|
155
159
|
<box flexDirection="row" flexGrow={1} overflow="hidden">
|
|
156
|
-
|
|
160
|
+
{/* The nav is this screen's bar, so it wears the bar's surface: panel
|
|
161
|
+
against the page the content pane keeps. */}
|
|
162
|
+
<box
|
|
163
|
+
width={navWidth}
|
|
164
|
+
flexDirection="row"
|
|
165
|
+
overflow="hidden"
|
|
166
|
+
backgroundColor={t.backgroundPanel}
|
|
167
|
+
>
|
|
157
168
|
<box width={navContentWidth} flexDirection="column" overflow="hidden">
|
|
158
169
|
<box paddingLeft={1} paddingRight={1}>
|
|
159
170
|
<text fg={t.textMuted}>Settings</text>
|
|
@@ -169,12 +180,12 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
169
180
|
index={index}
|
|
170
181
|
onClickIndex={handleSectionClick}
|
|
171
182
|
title={
|
|
172
|
-
<text fg={group.sectionId === current?.sectionId ?
|
|
183
|
+
<text fg={group.sectionId === current?.sectionId ? ink : t.textMuted}>
|
|
173
184
|
{`${group.glyph} ${group.label}`}
|
|
174
185
|
</text>
|
|
175
186
|
}
|
|
176
187
|
trailing={
|
|
177
|
-
group.sectionId === current?.sectionId ? <text fg={
|
|
188
|
+
group.sectionId === current?.sectionId ? <text fg={ink}>›</text> : undefined
|
|
178
189
|
}
|
|
179
190
|
/>
|
|
180
191
|
))}
|
|
@@ -185,19 +196,13 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
185
196
|
</text>
|
|
186
197
|
</box>
|
|
187
198
|
</box>
|
|
188
|
-
<box width={
|
|
199
|
+
<box width={NAV_GUTTER} flexShrink={0} backgroundColor={t.backgroundPanel} />
|
|
189
200
|
</box>
|
|
190
|
-
{/* The pane the terminal would be in, with the same
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
title={`${SETTINGS_GLYPH} Settings`}
|
|
196
|
-
padding={0}
|
|
197
|
-
flexDirection="column"
|
|
198
|
-
flexGrow={1}
|
|
199
|
-
backgroundColor={t.background}
|
|
200
|
-
>
|
|
201
|
+
{/* The pane the terminal would be in, with the same inset — padding where
|
|
202
|
+
that one has padding, and no frame, for the same reason. The titled
|
|
203
|
+
border said "Settings" a third time, after the nav heading and the
|
|
204
|
+
section header. */}
|
|
205
|
+
<box padding={1} flexDirection="column" flexGrow={1} backgroundColor={t.background}>
|
|
201
206
|
{/* Outside the scroll: the way to find a setting should not be a thing
|
|
202
207
|
you have to scroll back up to. */}
|
|
203
208
|
<box flexShrink={0} paddingLeft={PAGE_PAD} paddingRight={PAGE_PAD} paddingTop={1}>
|
|
@@ -212,14 +217,13 @@ export const SettingsView = memo(function SettingsView() {
|
|
|
212
217
|
>
|
|
213
218
|
<box flexDirection="column" paddingLeft={PAGE_PAD} paddingRight={PAGE_PAD}>
|
|
214
219
|
<TileRow tiles={tiles} usable={usable} />
|
|
215
|
-
<box
|
|
216
|
-
<Rule width={usable} />
|
|
217
|
-
</box>
|
|
220
|
+
<box height={1} flexShrink={0} />
|
|
218
221
|
{current === undefined ? (
|
|
219
222
|
<Muted>Nothing to configure here yet.</Muted>
|
|
220
223
|
) : (
|
|
221
224
|
<Section
|
|
222
225
|
glyph={current.glyph}
|
|
226
|
+
rule={false}
|
|
223
227
|
title={current.label}
|
|
224
228
|
// Pinned to the same right edge every note on the screen lands on.
|
|
225
229
|
note={String(current.rows.length)}
|
|
@@ -7,6 +7,7 @@ import { useAppStore } from '../../../state/app-store'
|
|
|
7
7
|
import { clampBarWidth } from '../../../state/bars'
|
|
8
8
|
import { dispatchGlobal } from '../../../state/dispatch-ref'
|
|
9
9
|
import { STATS_PAGES, statsPageAt } from '../../../state/stats-pages'
|
|
10
|
+
import { useSelectionInk } from '../../selection-ink'
|
|
10
11
|
import { useTheme } from '../../theme'
|
|
11
12
|
import { ListItem } from '../primitives/list-item'
|
|
12
13
|
import { AimuxPage } from './aimux-page'
|
|
@@ -26,6 +27,7 @@ const COLUMN_CONTENT_OPTIONS = { flexDirection: 'column' as const, gap: 0 }
|
|
|
26
27
|
*/
|
|
27
28
|
export const StatsView = memo(function StatsView() {
|
|
28
29
|
const t = useTheme()
|
|
30
|
+
const ink = useSelectionInk()
|
|
29
31
|
const stats = useAppStore((s) => s.stats)
|
|
30
32
|
const navWidth = useAppStore((s) => clampBarWidth(s.bars.left.width))
|
|
31
33
|
const dimensions = useTerminalDimensions()
|
|
@@ -104,11 +106,11 @@ export const StatsView = memo(function StatsView() {
|
|
|
104
106
|
index={index}
|
|
105
107
|
onClickIndex={handlePageClick}
|
|
106
108
|
title={
|
|
107
|
-
<text fg={index === stats.pageIndex ?
|
|
109
|
+
<text fg={index === stats.pageIndex ? ink : t.textMuted}>
|
|
108
110
|
{`${entry.glyph} ${entry.label}`}
|
|
109
111
|
</text>
|
|
110
112
|
}
|
|
111
|
-
trailing={index === stats.pageIndex ? <text fg={
|
|
113
|
+
trailing={index === stats.pageIndex ? <text fg={ink}>›</text> : undefined}
|
|
112
114
|
/>
|
|
113
115
|
))}
|
|
114
116
|
</box>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useBaseTheme } from './theme'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one legible colour on a selected list row.
|
|
5
|
+
*
|
|
6
|
+
* A selected row is filled with `primary`, so everything drawn on it has to read
|
|
7
|
+
* against that rather than against the modal behind it — the page background
|
|
8
|
+
* does, which is the same trick the status bar's accent tiles already use for
|
|
9
|
+
* their mode and version badges. Every tone the caller would otherwise use on
|
|
10
|
+
* that row (muted subtitle, warning mark, accent badge) collapses to this one:
|
|
11
|
+
* a fill that strong leaves room for exactly one ink.
|
|
12
|
+
*
|
|
13
|
+
* Always the opaque base, never the live theme. In transparent mode
|
|
14
|
+
* `t.background` resolves to 'transparent' and the text would be painted away,
|
|
15
|
+
* while the fill itself stays opaque — `primary` is not one of the chrome
|
|
16
|
+
* background tokens the transparent overlay rewrites.
|
|
17
|
+
*/
|
|
18
|
+
export function useSelectionInk(): string {
|
|
19
|
+
return useBaseTheme().background
|
|
20
|
+
}
|