mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-09 22:13:43 +08:00
Remove sample chips and move quick select down
This commit is contained in:
@@ -483,6 +483,9 @@ vm.colorAlpha("text", { target: { value: "128" } })
|
||||
assert.equal(vm.selectedColors.text, "#ABCDEF80")
|
||||
// The CEM gauge is marked outside CEM scenes, as an editing aid only.
|
||||
assert.match(OnroadLayoutPage.template, /v-if="widget\.kind === 'cem_gauge' && state\.showEditingAids && !state\.scene\.startsWith\('cem_'\)"/)
|
||||
// The Small screen shows the tap-areas chip under the layout instead of over it.
|
||||
assert.match(OnroadLayoutPage.template, /state\.profile !== 'compact'" class="gx-layout__stage-chip" aria-hidden="true"><i class="bi bi-grid-3x3-gap"><\/i>Tap areas/)
|
||||
assert.match(OnroadLayoutPage.template, /state\.profile === 'compact'" class="gx-layout__stage-chip gx-layout__stage-chip--below"/)
|
||||
// One scene dropdown, grouped, with every scene exactly once.
|
||||
assert.deepEqual(vm.sceneGroups.map(group => group.label), ["Driving", "Conditional experimental", "Speed limit"])
|
||||
assert.deepEqual(vm.sceneGroups.flatMap(group => group.scenes.map(scene => scene.id)).sort(), vm.scenes.map(scene => scene.id).sort())
|
||||
|
||||
@@ -643,6 +643,7 @@ input[type="range"].gx-slider:active::-moz-range-thumb {
|
||||
/* Tap-area overlay label sits on the preview while the grid toggle is on. */
|
||||
.gx-layout__preview-card > .gx-layout__stage { position: relative; margin-inline: 0; min-width: 0; }
|
||||
.gx-layout__stage-chip { position: absolute; top: 6px; right: 6px; z-index: 1; display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid #b799ff88; border-radius: var(--radius-full); background: rgba(22, 18, 31, .82); color: #d8c8ff; font-size: var(--fs-xs); font-weight: var(--fw-bold); white-space: nowrap; pointer-events: none; }
|
||||
.gx-layout__stage-chip--below { position: static; justify-self: end; }
|
||||
.gx-layout__stage-chip--start { right: auto; left: 6px; max-width: calc(100% - 12px); overflow: hidden; text-overflow: ellipsis; display: block; }
|
||||
/* Covers the whole bar, buttons included, so its size never changes mid-drag. */
|
||||
.gx-layout__savebar-snap { position: absolute; inset: 0; z-index: 1; display: flex; align-items: center; gap: 6px; padding: 0 14px; border-radius: inherit; background: inherit; color: var(--primary); font-weight: var(--fw-bold); pointer-events: none; }
|
||||
|
||||
@@ -1249,8 +1249,8 @@ export const OnroadLayoutPage = {
|
||||
<p v-if="state.backdrop.status === 'unavailable'" class="gx-note" role="status">{{ state.backdrop.url ? 'The comma screen could not refresh. Showing the last one.' : 'The comma screen is unavailable. Showing a sketch.' }}</p>
|
||||
<p v-if="state.art.status === 'unavailable'" class="gx-note" role="status">{{ state.art.url ? 'Widget preview could not refresh. Showing the last preview.' : 'Widget preview unavailable. Placement controls remain available.' }}</p>
|
||||
<div class="gx-layout__stage">
|
||||
<span v-if="state.showEditingAids && state.snapGuides.length && !snapInBar" class="gx-layout__stage-chip gx-layout__stage-chip--start" :class="{'gx-layout__stage-chip--with-tap-areas': !projection && state.showFavoriteZones}" aria-hidden="true">{{ snapSummary }}</span>
|
||||
<span v-if="!projection && state.showFavoriteZones && state.showEditingAids" class="gx-layout__stage-chip" aria-hidden="true"><i class="bi bi-grid-3x3-gap"></i>Tap areas</span>
|
||||
<span v-if="state.showEditingAids && state.snapGuides.length && !snapInBar" class="gx-layout__stage-chip gx-layout__stage-chip--start" :class="{'gx-layout__stage-chip--with-tap-areas': !projection && state.showFavoriteZones && state.profile !== 'compact'}" aria-hidden="true">{{ snapSummary }}</span>
|
||||
<span v-if="!projection && state.showFavoriteZones && state.showEditingAids && state.profile !== 'compact'" class="gx-layout__stage-chip" aria-hidden="true"><i class="bi bi-grid-3x3-gap"></i>Tap areas</span>
|
||||
<svg ref="preview" class="gx-layout__preview" :style="{'--layout-glow': state.profile === 'compact' ? '4px' : '14px'}" :viewBox="'0 0 ' + profile.width + ' ' + profile.height" :width="profile.width" :height="profile.height"
|
||||
@pointermove="moveDrag" @pointerup="endDrag" @pointercancel="cancelDrag" @lostpointercapture="cancelDrag"
|
||||
role="group" :aria-label="projection ? 'Android Auto layout preview' : (state.profile === 'large' ? 'Big' : 'Small') + ' layout preview'">
|
||||
@@ -1275,7 +1275,6 @@ export const OnroadLayoutPage = {
|
||||
opacity=".7" :stroke-width="state.profile === 'compact' ? 2 : 5" />
|
||||
<rect v-if="!projection" :x="state.profile === 'large' ? 1860 : 476" y="0" :width="state.profile === 'large' ? 300 : 60" :height="profile.height" fill="#090c10" />
|
||||
<g v-if="!projection && state.profile === 'large'" aria-hidden="true" pointer-events="none">
|
||||
<text x="1874" y="17" font-size="14" font-weight="600" fill="#ffffffd2">SAMPLE DATA</text>
|
||||
<g v-for="(sample, index) in [{label:'ACCEL',value:'1.08 ft/s²',accent:'#ffffff'}, {label:'STEER DELAY',value:'0.15000',accent:'#3b82f6'}, {label:'FRICTION',value:'0.12000',accent:'#22c55e'}, {label:'LAT ACCEL',value:'2.50000',accent:'#3b82f6'}, {label:'LATERAL %',value:'78.00%',accent:'#ffffff'}, {label:'TORQUE %',value:'42%',accent:'#ffffff'}, {label:'CHESTNUT',value:'',accent:'#ffffff'}]" :key="sample.label" :transform="'translate(1873 ' + (24 + index * 150) + ')'">
|
||||
<rect width="275" height="126" rx="16" fill="#000000" stroke="#ffffff55" stroke-width="2" />
|
||||
<rect x="253" y="4" width="18" height="118" rx="4" :fill="sample.accent" />
|
||||
@@ -1328,6 +1327,8 @@ export const OnroadLayoutPage = {
|
||||
<rect v-if="state.showEditingAids && selectionBox" class="gx-layout__outline" :x="selectionBox.x" :y="selectionBox.y" :width="selectionBox.width" :height="selectionBox.height" rx="4" fill="none" stroke="#d4baff" stroke-width="2" vector-effect="non-scaling-stroke" />
|
||||
</svg>
|
||||
</div>
|
||||
<!-- The Small screen is too short to carry the chip over the layout; it sits underneath instead. -->
|
||||
<span v-if="!projection && state.showFavoriteZones && state.showEditingAids && state.profile === 'compact'" class="gx-layout__stage-chip gx-layout__stage-chip--below" aria-hidden="true"><i class="bi bi-grid-3x3-gap"></i>Tap areas</span>
|
||||
<div v-show="state.tipsOpen" id="gx-layout-tips" class="gx-layout__tips">
|
||||
<p class="gx-note">Drag widgets to place them. Behind them is the comma's own screen for the chosen scene, including its border, road and alerts. Readings are samples.</p>
|
||||
<p class="gx-note">Widgets snap to the screen edges, the centre and other widgets. Turn snapping off with the magnet button, or hold Option (Alt) while dragging.</p>
|
||||
|
||||
@@ -15,6 +15,8 @@ class OnroadVisualPreview:
|
||||
cem_reason: str | None = None
|
||||
curve_curvature: float | None = None
|
||||
label: str = "SYNTHETIC REPLAY PREVIEW"
|
||||
# The Galaxy layout editor's sample: draw exactly as the car would, without the replay labels.
|
||||
layout_sample: bool = False
|
||||
|
||||
|
||||
def parse_flags(raw: str) -> frozenset[str]:
|
||||
|
||||
@@ -93,7 +93,7 @@ def sample_state(scene: str, document: dict) -> OnroadState:
|
||||
longitudinal_active=longitudinal, stock_cruise_active=False,
|
||||
slc_system_long_available=longitudinal, torque_utilization=0.78 if lateral else 0.0,
|
||||
customization=document,
|
||||
visual_preview=OnroadVisualPreview(cem_reason=CEM_SCENE_REASONS[scene]) if scene in CEM_SCENE_REASONS else None)
|
||||
visual_preview=OnroadVisualPreview(cem_reason=CEM_SCENE_REASONS[scene], layout_sample=True) if scene in CEM_SCENE_REASONS else None)
|
||||
|
||||
if scene == 'cem_lead':
|
||||
state = replace(state, conditional_lead=ConditionalLead(12.0, 0.0))
|
||||
@@ -281,7 +281,7 @@ class _Canvas:
|
||||
finally:
|
||||
rl.rl_pop_matrix()
|
||||
|
||||
def render(self, state: OnroadState, scene: str, *, label: bool = True) -> bytes:
|
||||
def render(self, state: OnroadState, scene: str) -> bytes:
|
||||
if self.view is None or self.fonts is None or self.target is None:
|
||||
raise RuntimeError("Layout preview resources are closed")
|
||||
self._settle(state)
|
||||
@@ -299,8 +299,6 @@ class _Canvas:
|
||||
self.fonts.draw('StarPilot', FontRole.BRAND, 30, width - 210, height - 90)
|
||||
if self.profile == Profile.LARGE and self.viewport is None:
|
||||
render_sidebar(self.fonts, rl.Rectangle(width - SIDEBAR_WIDTH, 0, SIDEBAR_WIDTH, height))
|
||||
if label:
|
||||
self._label(scene)
|
||||
finally:
|
||||
rl.end_texture_mode()
|
||||
image = rl.load_image_from_texture(self.target.texture)
|
||||
@@ -393,21 +391,6 @@ class _Canvas:
|
||||
self.view.torque_bar._torque_filter.x = state.torque_utilization if state.lateral_active else 0.0
|
||||
self.view.torque_bar._alpha_filter.x = 1.0 if state.lateral_active else 0.0
|
||||
|
||||
def _label(self, scene: str) -> None:
|
||||
if self.fonts is None:
|
||||
return
|
||||
large = self.profile == Profile.LARGE
|
||||
names = {"experimental": "MANUAL EXPERIMENTAL", "cem_stop_light": "CEM TRAFFIC LIGHT",
|
||||
"cem_lead": "CEM STOPPED LEAD", "cem_curve": "CEM CURVE"}
|
||||
label = "SAMPLE: " + names.get(scene, scene.replace("_", " ").upper())
|
||||
size = 27 if large else 14
|
||||
measured = self.fonts.measure(label, FontRole.SEMI_BOLD, size)
|
||||
x = (1860 if large else 476) - measured.width - (38 if large else 8)
|
||||
y = 1000 if large else 213
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(x - 8, y - 3, measured.width + 16, measured.height + 6),
|
||||
0.2, 4, rl.Color(0, 0, 0, 220))
|
||||
self.fonts.draw(label, FontRole.SEMI_BOLD, size, x, y, rl.Color(255, 255, 255, 255))
|
||||
|
||||
def close(self) -> None:
|
||||
if self.map is not None:
|
||||
self.map.close()
|
||||
@@ -470,7 +453,7 @@ class LayoutPreviewRenderer:
|
||||
# Everything the car draws except the movable widgets, which the editor places over it.
|
||||
# A non-empty order that names no widget keeps the ordered path and draws none of them.
|
||||
order = {**state.customization.get('widgetOrder', {}), str(profile): ['backdrop']}
|
||||
return self._canvas.render(replace(state, customization={**state.customization, 'widgetOrder': order}), scene, label=False)
|
||||
return self._canvas.render(replace(state, customization={**state.customization, 'widgetOrder': order}), scene)
|
||||
return self._canvas.render(state, scene)
|
||||
|
||||
def expire(self) -> None:
|
||||
|
||||
@@ -65,5 +65,4 @@ def render_sidebar(fonts: BitmapFonts, frame: rl.Rectangle,
|
||||
fonts.draw(line, FontRole.SEMI_BOLD, font_size,
|
||||
rect.x + (rect.width - measured.width) / 2, y, rl.WHITE)
|
||||
y += measured.height
|
||||
fonts.draw("SAMPLE DATA", FontRole.SEMI_BOLD, 14, frame.x + 14, frame.y + 3,
|
||||
rl.Color(255, 255, 255, 210))
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ class OnroadView:
|
||||
def _preview_badge(self, state: OnroadState) -> None:
|
||||
"""Keep synthetic replay visibly identified even over a full alert."""
|
||||
preview = state.visual_preview
|
||||
if preview is None:
|
||||
if preview is None or preview.layout_sample:
|
||||
return
|
||||
flags = []
|
||||
if preview.cem_reason is not None:
|
||||
@@ -422,7 +422,9 @@ class OnroadView:
|
||||
self.fonts.draw(label, FontRole.SEMI_BOLD, 29, 1390 + right_shift, 56,
|
||||
curve_color)
|
||||
mode = conditional_status(state)
|
||||
if mode is not None and state.alert.size != AlertSize.FULL:
|
||||
# The layout editor leaves out the mode caption; its CEM scenes are named in the editor itself.
|
||||
sample = state.visual_preview is not None and state.visual_preview.layout_sample
|
||||
if mode is not None and state.alert.size != AlertSize.FULL and not sample:
|
||||
family, reason, mode_color = mode
|
||||
mode_y = 101 if label is not None else 56
|
||||
self.fonts.draw(f'{family} {reason}', FontRole.SEMI_BOLD, 25, 1390 + right_shift, mode_y, mode_color)
|
||||
|
||||
@@ -48,6 +48,10 @@ def status(state: OnroadState) -> tuple[str, str, rl.Color] | None:
|
||||
if preview is not None and preview.cem_reason in ('CURVE', 'LEAD', 'STOP LIGHT', 'SPEED'):
|
||||
if state.alert.size == AlertSize.FULL:
|
||||
return None
|
||||
if preview.layout_sample:
|
||||
# The layout editor's sample keeps the car's own reason (the gauge draws from it), not replay wording.
|
||||
detail = _REASONS.get(reason(state))
|
||||
return ('CEM', detail, rl.Color(112, 192, 216, 255)) if detail else None
|
||||
return 'CEM', f'PREVIEW {preview.cem_reason}', rl.Color(112, 192, 216, 255)
|
||||
value = (state.conditional_effective if state.longitudinal_active else None) or getattr(state, 'conditional_perception', None)
|
||||
if value is None or value.choice not in (ModeChoice.CEM, ModeChoice.CCM):
|
||||
|
||||
@@ -32,7 +32,7 @@ class DeveloperPreviewTests(unittest.TestCase):
|
||||
value = preview_at(frozenset(("cem", "csc")), 3.0)
|
||||
with self.assertRaises(FrozenInstanceError):
|
||||
value.__setattr__("cem_reason", "ACTUAL")
|
||||
self.assertEqual(set(vars(value)), {"cem_reason", "curve_curvature", "label"})
|
||||
self.assertEqual(set(vars(value)), {"cem_reason", "curve_curvature", "label", "layout_sample"})
|
||||
|
||||
def test_invalid_or_duplicate_flags_and_time_rejected(self):
|
||||
for raw in ("cem,cem", "csc,bogus", "CEM", ",cem"):
|
||||
|
||||
@@ -80,7 +80,12 @@ class LayoutPreviewRendererTests(unittest.TestCase):
|
||||
self.assertTrue(sample.experimental_enabled)
|
||||
self.assertEqual(sample.visual_preview.cem_reason, reason)
|
||||
self.assertIsNone(sample.conditional_effective)
|
||||
self.assertEqual(sample.visual_preview.label, "SYNTHETIC REPLAY PREVIEW")
|
||||
self.assertTrue(sample.visual_preview.layout_sample) # Drawn as the car would, without replay labels.
|
||||
from openpilot.starpilot.ui.onroad_conditional import status
|
||||
# The gauge still reads the real reason, but the mode caption itself is not drawn in the editor.
|
||||
family, detail, _ = status(sample)
|
||||
self.assertEqual(family, "CEM")
|
||||
self.assertFalse(detail.startswith("PREVIEW"))
|
||||
self.assertEqual(sample_state("cem_lead", document).speed_mps, 0.0)
|
||||
with self.assertRaises(ValueError):
|
||||
sample_state("live", document)
|
||||
@@ -120,7 +125,7 @@ class LayoutPreviewRendererTests(unittest.TestCase):
|
||||
self.assertEqual(renderer({"document": document, "profile": "compact", "scene": "braking", "art": "backdrop"}), b"png")
|
||||
state, scene = canvas.render.call_args.args
|
||||
self.assertEqual(scene, "braking")
|
||||
self.assertEqual(canvas.render.call_args.kwargs, {"label": False})
|
||||
self.assertEqual(canvas.render.call_args.kwargs, {})
|
||||
# An order naming no widget keeps the ordered path, so no movable widget is drawn.
|
||||
self.assertEqual(state.customization["widgetOrder"]["compact"], ["backdrop"])
|
||||
self.assertEqual(state.customization["layouts"], document["layouts"])
|
||||
@@ -200,7 +205,7 @@ class LayoutPreviewRendererTests(unittest.TestCase):
|
||||
patch("openpilot.starpilot.ui.layout_preview_sidebar.rl.draw_rectangle_rounded_lines_ex"):
|
||||
render_sidebar(fonts, rl.Rectangle(1860, 0, 300, 1080), metric)
|
||||
labels = [call.args[0] for call in fonts.draw.call_args_list]
|
||||
self.assertEqual(labels, ["ACCEL", "1.08 ft/s²", "SAMPLE DATA"])
|
||||
self.assertEqual(labels, ["ACCEL", "1.08 ft/s²"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user