cabana: smooth startup layout (#38794)

* cabana: smooth startup layout

* cabana: remove camera tab startup comment

* cabana: initialize dock geometry on the first frame

* cabana: remove route loading placeholder
This commit is contained in:
Trey Moen
2026-09-07 11:50:13 -07:00
committed by GitHub
parent 65c411433b
commit cb70ba89aa
5 changed files with 37 additions and 14 deletions
+4
View File
@@ -132,6 +132,8 @@ public:
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
#endif
// Restore geometry and render the initial layout before mapping the window.
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
window_ = glfwCreateWindow(1600, 900, "Cabana", nullptr, nullptr);
if (window_ == nullptr) {
glfwTerminate();
@@ -212,6 +214,8 @@ int run(std::unique_ptr<AbstractStream> stream, StreamLoader stream_loader, cons
inistate::applyWindowGeometry(glfw.window());
MainWindow win(glfw.window(), std::move(stream), std::move(stream_loader), dbc_file);
renderFrame(glfw.window(), &win);
glfwShowWindow(glfw.window());
while (!win.exited()) {
if (g_signal_exit.exchange(false)) {
printf("\nexiting...\n");
+18 -11
View File
@@ -184,7 +184,7 @@ void MainWindow::drawMenuBar() {
if (ImGui::MenuItem("Full Screen", "Ctrl+F11")) toggleFullScreen();
ImGui::Separator();
ImGui::MenuItem(messages_widget_ ? messages_widget_->title().c_str() : "MESSAGES", nullptr, &messages_visible_);
ImGui::MenuItem(video_dock_title_.empty() ? "##video_dock" : video_dock_title_.c_str(), nullptr, &video_visible_);
ImGui::MenuItem(video_dock_title_.empty() ? "Video" : video_dock_title_.c_str(), nullptr, &video_visible_);
ImGui::Separator();
if (ImGui::MenuItem("Reset Window Layout")) {
messages_visible_ = video_visible_ = true;
@@ -789,8 +789,10 @@ void MainWindow::drawWaitDialog() {
void MainWindow::drawDockspace() {
const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
// Use the menu bar's current-frame reservation, including on the first frame.
const ImRect work_rect = static_cast<const ImGuiViewportP *>(viewport)->GetBuildWorkRect();
ImGui::SetNextWindowPos(work_rect.Min);
ImGui::SetNextWindowSize(work_rect.GetSize());
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
@@ -813,6 +815,7 @@ void MainWindow::drawDockspace() {
// messages left, video (with charts) right, center widget in the middle
ImGui::DockBuilderRemoveNode(dock_id);
ImGui::DockBuilderAddNode(dock_id, ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodePos(dock_id, ImGui::GetCursorScreenPos());
ImGui::DockBuilderSetNodeSize(dock_id, dock_size);
ImGuiID center = dock_id, left = 0, right = 0;
ImGui::DockBuilderSplitNode(center, ImGuiDir_Left, 0.28f, &left, &center);
@@ -856,11 +859,13 @@ bool beginPanel(const char *name, bool *open, ImGuiWindowFlags flags = 0) {
} // namespace
void MainWindow::drawMessagesPanel() {
const std::string name = messages_widget_->title() + MESSAGES_PANEL_ID;
const std::string name = (messages_widget_ ? messages_widget_->title() : "MESSAGES") + std::string(MESSAGES_PANEL_ID);
setNextPanelClass();
if (beginPanel(name.c_str(), &messages_visible_)) {
help_overlay_.add(messages_widget_->whatsThis(), ImGui::GetCurrentWindow()->Rect());
messages_widget_->draw();
if (messages_widget_) {
help_overlay_.add(messages_widget_->whatsThis(), ImGui::GetCurrentWindow()->Rect());
messages_widget_->draw();
}
}
const bool floating = floatingOut();
ImGui::End();
@@ -868,13 +873,13 @@ void MainWindow::drawMessagesPanel() {
}
void MainWindow::drawVideoPanel() {
const std::string name = video_dock_title_ + VIDEO_PANEL;
const std::string name = (video_dock_title_.empty() ? "Video" : video_dock_title_) + VIDEO_PANEL;
setNextPanelClass();
const bool video_open = beginPanel(name.c_str(), &video_visible_);
const bool floating = floatingOut();
if (!video_open) {
if (video_widget_ && !video_open) {
video_widget_->setVisible(false); // the dock is collapsed or tabbed behind another one, like hideEvent
} else {
} else if (video_widget_) {
const ImVec2 avail = ImGui::GetContentRegionAvail();
const bool live = can->liveStreaming();
// the bordered child pads its content, so the heights the widget asks for grow by the padding
@@ -962,9 +967,11 @@ void MainWindow::draw() {
ImGui::EndChild();
}
ImGui::End();
if (messages_widget_ && messages_visible_) drawMessagesPanel();
// Submit the same dock windows while loading, so ImGui doesn't collapse their
// nodes and then redistribute the layout when the stream's widgets arrive.
if (messages_visible_) drawMessagesPanel();
if (video_widget_ && !video_visible_) video_widget_->setVisible(false);
if (video_widget_ && video_visible_) drawVideoPanel();
if (video_visible_) drawVideoPanel();
if (charts_widget_ && charts_floating_) {
bool open = true;
ImGui::SetNextWindowSize(ImGui::GetMainViewport()->WorkSize, ImGuiCond_Appearing);
@@ -95,7 +95,7 @@ float CameraWidget::frameAspectRatio() const {
if (frame_texture_.width > 0 && frame_texture_.height > 0) {
return (float)frame_texture_.width / frame_texture_.height;
}
return 1928.0f / 1208.0f; // the road camera, until the first frame arrives
return DEFAULT_CAMERA_ASPECT_RATIO; // the road camera, until the first frame arrives
}
void CameraWidget::paint() {
@@ -18,6 +18,8 @@
#include "tools/cabana/core/observable.h"
#include "msgq/visionipc/visionipc_client.h"
constexpr float DEFAULT_CAMERA_ASPECT_RATIO = 1928.0f / 1208.0f;
// Center-crop the source to fill the destination without stretching.
inline ImVec2 videoFillUv(const ImVec2 &size, float aspect_ratio) {
const float ratio = size.x / size.y / aspect_ratio;
@@ -261,6 +261,16 @@ void VideoWidget::createCameraWidget() {
if (index != -1) cam_widget_->setStreamType((VisionStreamType)camera_tab_->tabData(index));
}));
connections_.push_back(static_cast<ReplayStream *>(can)->qLogLoaded.connect([this](std::shared_ptr<LogReader> qlog) { cam_widget_->parseQLog(qlog); }));
if (auto *replay = getReplay(); replay && !replay->hasFlag(REPLAY_FLAG_NO_VIPC)) {
std::set<VisionStreamType> streams;
for (const auto &[num, segment] : replay->route().segments()) {
if (!segment.narrow_road_cam.empty() || !segment.qcamera.empty()) streams.insert(VISION_STREAM_NARROW_ROAD);
if (replay->hasFlag(REPLAY_FLAG_CABIN_CAMERA) && !segment.cabin_cam.empty()) streams.insert(VISION_STREAM_CABIN);
if (replay->hasFlag(REPLAY_FLAG_WIDE_ROAD) && !segment.wide_road_cam.empty()) streams.insert(VISION_STREAM_WIDE_ROAD);
}
vipcAvailableStreamsUpdated(streams);
}
}
void VideoWidget::drawCameraWidget() {
@@ -344,10 +354,10 @@ float VideoWidget::sizeHintHeight() const {
return MIN_VIDEO_HEIGHT + SLIDER_HEIGHT + toolbarHeight();
}
// the video pane opens with the camera at its natural aspect ratio, filling the width of the dock
// Keep the pane's default proportions stable as frames arrive or cameras change.
float VideoWidget::defaultHeight(float width) const {
if (!cam_widget_) return toolbarHeight(); // live streams have no camera or slider
const float cam_height = std::max((float)MIN_VIDEO_HEIGHT, width / cam_widget_->frameAspectRatio());
const float cam_height = std::max((float)MIN_VIDEO_HEIGHT, width / DEFAULT_CAMERA_ASPECT_RATIO);
const float tab_height = camera_tab_->count() >= 2 ? ImGui::GetFrameHeight() : 0.0f;
return cam_height + tab_height + SLIDER_HEIGHT + toolbarHeight();
}