From 12a4012ee551ae16864adb337fc03d370850d602 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 14 Jun 2026 13:41:58 +0300 Subject: [PATCH 1/3] feat(theme): sync window chrome with QueryaThemeScope workbench tokens Drive title bar background and bitsdojo window button colors from workbench canvas, surface, and mutedForeground instead of shadcn ColorScheme alone. --- lib/features/main_screen/main_screen.dart | 192 +---------------- .../main_screen/querya_window_title_bar.dart | 201 ++++++++++++++++++ 2 files changed, 207 insertions(+), 186 deletions(-) create mode 100644 lib/features/main_screen/querya_window_title_bar.dart diff --git a/lib/features/main_screen/main_screen.dart b/lib/features/main_screen/main_screen.dart index 2250f616..278765c4 100644 --- a/lib/features/main_screen/main_screen.dart +++ b/lib/features/main_screen/main_screen.dart @@ -6,12 +6,9 @@ import 'package:flutter/material.dart' as material Scaffold, Container, MainAxisSize, - GestureDetector, MouseRegion, SystemMouseCursors, HitTestBehavior, - Icons, - Icon, BuildContext, Widget, RepaintBoundary; @@ -20,11 +17,10 @@ import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:querya_desktop/features/connections/connection_creation_flow.dart'; import 'package:querya_desktop/features/connections/connections_panel.dart'; +import 'package:querya_desktop/features/main_screen/querya_window_title_bar.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; import 'package:querya_desktop/features/mysql/mysql_object_kind.dart'; import 'package:querya_desktop/features/postgresql/postgres_object_kind.dart'; -import 'package:querya_desktop/features/connections/driver_manager_dialog.dart'; -import 'package:querya_desktop/features/settings/preferences_dialog.dart'; import 'main_screen_workspace_state.dart'; import 'workspace_panel.dart'; @@ -129,19 +125,18 @@ class _MainScreenState extends State { @override material.Widget build(material.BuildContext context) { - final scheme = Theme.of(context).colorScheme; + final wb = context.workbench; return material.Scaffold( - backgroundColor: scheme.background, + backgroundColor: wb.canvas, body: WindowBorder( - color: scheme.border.withValues(alpha: 0.35), + color: wb.borderSubtle.withValues(alpha: 0.35), width: 1, child: Column( children: [ - _CustomTitleBar( - theme: scheme, + QueryaWindowTitleBar( onNewDatabaseConnection: _onNewDatabaseConnectionFromMenu, ), - Divider(height: 1, color: scheme.border.withValues(alpha: 0.22)), + Divider(height: 1, color: wb.borderSubtle.withValues(alpha: 0.22)), Expanded( child: _MainContentSplit( connectionsPanelKey: _connectionsPanelKey, @@ -391,178 +386,3 @@ class _VerticalResizeHandle extends StatelessWidget { ); } } - -class _CustomTitleBar extends StatefulWidget { - const _CustomTitleBar({ - required this.theme, - required this.onNewDatabaseConnection, - }); - - final ColorScheme theme; - final Future Function() onNewDatabaseConnection; - - @override - State<_CustomTitleBar> createState() => _CustomTitleBarState(); -} - -class _CustomTitleBarState extends State<_CustomTitleBar> { - @override - material.Widget build(material.BuildContext context) { - final c = widget.theme; - final onDestructive = context.workbench.onAccent; - final buttonColors = WindowButtonColors( - iconNormal: c.mutedForeground, - mouseOver: c.muted.withValues(alpha: 0.5), - mouseDown: c.muted.withValues(alpha: 0.7), - iconMouseOver: c.foreground, - iconMouseDown: c.foreground, - ); - final closeButtonColors = WindowButtonColors( - iconNormal: c.mutedForeground, - mouseOver: c.destructive, - mouseDown: c.destructive.withValues(alpha: 0.85), - iconMouseOver: onDestructive, - iconMouseDown: onDestructive, - ); - - return material.Container( - height: 40, - color: c.background, - child: WindowTitleBarBox( - child: Row( - children: [ - Expanded( - child: MoveWindow( - child: Row( - children: [ - const SizedBox(width: 16), - material.Icon( - material.Icons.search_rounded, - size: 18, - color: context.workbench.accent, - ), - const Gap(8), - const Text('Querya').semiBold().small(), - const Gap(24), - Menubar( - border: false, - popoverOffset: const Offset(0, 8), - children: [ - MenuButton( - subMenu: [ - MenuButton( - onPressed: (_) {}, child: const Text('New')), - MenuButton( - onPressed: (_) {}, - child: const Text('Open...')), - MenuButton( - onPressed: (_) {}, child: const Text('Save')), - const MenuDivider(), - MenuButton( - onPressed: (_) {}, child: const Text('Exit')), - ], - child: const Text('File'), - ), - MenuButton( - subMenu: [ - MenuButton( - leading: const material.Icon( - material.Icons.tune_rounded, - size: 18, - ), - onPressed: (ctx) => showPreferencesDialog(ctx), - child: const Text('Preferences…'), - ), - ], - child: const Text('Edit'), - ), - MenuButton( - subMenu: [ - MenuButton( - leading: const material.Icon( - material.Icons.add_link_rounded, size: 18), - trailing: - const Text('Shift+Ctrl+N').xSmall().muted(), - onPressed: (_) => - widget.onNewDatabaseConnection(), - child: const Text('New Database Connection'), - ), - MenuButton( - leading: const material.Icon( - material.Icons.link_rounded, size: 18), - onPressed: (_) {}, - child: const Text('New Connection from URL'), - ), - MenuButton( - leading: const material.Icon( - material.Icons.settings_rounded, size: 18), - onPressed: (ctx) => showDriverManagerDialog(ctx), - child: const Text('Driver Manager'), - ), - const MenuDivider(), - MenuButton( - enabled: false, - leading: const material.Icon( - material.Icons.power_rounded, size: 18), - onPressed: (_) {}, - child: const Text('Connect'), - ), - MenuButton( - leading: const material.Icon( - material.Icons.refresh_rounded, size: 18), - onPressed: (_) {}, - child: const Text('Invalidate/Reconnect'), - ), - MenuButton( - leading: const material.Icon( - material.Icons.power_off_rounded, size: 18), - onPressed: (_) {}, - child: const Text('Disconnect'), - ), - MenuButton( - onPressed: (_) {}, - child: const Text('Disconnect All')), - MenuButton( - onPressed: (_) {}, - child: const Text('Disconnect Others')), - const MenuDivider(), - MenuButton( - leading: const material.Icon( - material.Icons.lock_outline_rounded, - size: 18), - onPressed: (_) {}, - child: const Text('Read-only'), - ), - ], - child: const Text('Connection'), - ), - MenuButton( - subMenu: [ - MenuButton( - onPressed: (_) {}, child: const Text('About')), - MenuButton( - onPressed: (_) {}, - child: const Text('Documentation')), - ], - child: const Text('Help'), - ), - ], - ), - ], - ), - ), - ), - Row( - mainAxisSize: material.MainAxisSize.min, - children: [ - MinimizeWindowButton(colors: buttonColors), - MaximizeWindowButton(colors: buttonColors), - CloseWindowButton(colors: closeButtonColors), - ], - ) - ], - ), - ), - ); - } -} diff --git a/lib/features/main_screen/querya_window_title_bar.dart b/lib/features/main_screen/querya_window_title_bar.dart new file mode 100644 index 00000000..ef993854 --- /dev/null +++ b/lib/features/main_screen/querya_window_title_bar.dart @@ -0,0 +1,201 @@ +import 'dart:ui' show Color; + +import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' as material + show + BuildContext, + Container, + Icon, + Icons, + MainAxisSize, + Widget; +import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; +import 'package:querya_desktop/features/connections/driver_manager_dialog.dart'; +import 'package:querya_desktop/features/settings/preferences_dialog.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +/// Custom bitsdojo title bar styled from [QueryaThemeScope] workbench tokens. +class QueryaWindowTitleBar extends StatelessWidget { + const QueryaWindowTitleBar({ + super.key, + required this.onNewDatabaseConnection, + }); + + final Future Function() onNewDatabaseConnection; + + @visibleForTesting + static Color titleBarBackground(BuildContext context) => + context.workbench.canvas; + + @visibleForTesting + static WindowButtonColors windowButtonColors(BuildContext context) { + final wb = context.workbench; + final cs = Theme.of(context).colorScheme; + return WindowButtonColors( + iconNormal: wb.mutedForeground, + mouseOver: wb.surface.withValues(alpha: 0.85), + mouseDown: wb.borderSubtle.withValues(alpha: 0.55), + iconMouseOver: cs.foreground, + iconMouseDown: cs.foreground, + ); + } + + @visibleForTesting + static WindowButtonColors closeButtonColors(BuildContext context) { + final wb = context.workbench; + return WindowButtonColors( + iconNormal: wb.mutedForeground, + mouseOver: wb.destructive, + mouseDown: wb.destructive.withValues(alpha: 0.85), + iconMouseOver: wb.onAccent, + iconMouseDown: wb.onAccent, + ); + } + + @override + material.Widget build(material.BuildContext context) { + final wb = context.workbench; + final buttonColors = windowButtonColors(context); + final closeButtonColors = QueryaWindowTitleBar.closeButtonColors(context); + + return material.Container( + height: 40, + color: titleBarBackground(context), + child: WindowTitleBarBox( + child: Row( + children: [ + Expanded( + child: MoveWindow( + child: Row( + children: [ + const SizedBox(width: 16), + material.Icon( + material.Icons.search_rounded, + size: 18, + color: wb.accent, + ), + const Gap(8), + const Text('Querya').semiBold().small(), + const Gap(24), + Menubar( + border: false, + popoverOffset: const Offset(0, 8), + children: [ + MenuButton( + subMenu: [ + MenuButton( + onPressed: (_) {}, child: const Text('New')), + MenuButton( + onPressed: (_) {}, + child: const Text('Open...')), + MenuButton( + onPressed: (_) {}, child: const Text('Save')), + const MenuDivider(), + MenuButton( + onPressed: (_) {}, child: const Text('Exit')), + ], + child: const Text('File'), + ), + MenuButton( + subMenu: [ + MenuButton( + leading: const material.Icon( + material.Icons.tune_rounded, + size: 18, + ), + onPressed: (ctx) => showPreferencesDialog(ctx), + child: const Text('Preferences…'), + ), + ], + child: const Text('Edit'), + ), + MenuButton( + subMenu: [ + MenuButton( + leading: const material.Icon( + material.Icons.add_link_rounded, size: 18), + trailing: + const Text('Shift+Ctrl+N').xSmall().muted(), + onPressed: (_) => onNewDatabaseConnection(), + child: const Text('New Database Connection'), + ), + MenuButton( + leading: const material.Icon( + material.Icons.link_rounded, size: 18), + onPressed: (_) {}, + child: const Text('New Connection from URL'), + ), + MenuButton( + leading: const material.Icon( + material.Icons.settings_rounded, size: 18), + onPressed: (ctx) => showDriverManagerDialog(ctx), + child: const Text('Driver Manager'), + ), + const MenuDivider(), + MenuButton( + enabled: false, + leading: const material.Icon( + material.Icons.power_rounded, size: 18), + onPressed: (_) {}, + child: const Text('Connect'), + ), + MenuButton( + leading: const material.Icon( + material.Icons.refresh_rounded, size: 18), + onPressed: (_) {}, + child: const Text('Invalidate/Reconnect'), + ), + MenuButton( + leading: const material.Icon( + material.Icons.power_off_rounded, size: 18), + onPressed: (_) {}, + child: const Text('Disconnect'), + ), + MenuButton( + onPressed: (_) {}, + child: const Text('Disconnect All')), + MenuButton( + onPressed: (_) {}, + child: const Text('Disconnect Others')), + const MenuDivider(), + MenuButton( + leading: const material.Icon( + material.Icons.lock_outline_rounded, + size: 18), + onPressed: (_) {}, + child: const Text('Read-only'), + ), + ], + child: const Text('Connection'), + ), + MenuButton( + subMenu: [ + MenuButton( + onPressed: (_) {}, child: const Text('About')), + MenuButton( + onPressed: (_) {}, + child: const Text('Documentation')), + ], + child: const Text('Help'), + ), + ], + ), + ], + ), + ), + ), + Row( + mainAxisSize: material.MainAxisSize.min, + children: [ + MinimizeWindowButton(colors: buttonColors), + MaximizeWindowButton(colors: buttonColors), + CloseWindowButton(colors: closeButtonColors), + ], + ) + ], + ), + ), + ); + } +} From 1d69ec17329787983c610492ef2a2e53a8e0b823 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 14 Jun 2026 13:41:58 +0300 Subject: [PATCH 2/3] test(theme): cover window chrome styling from QueryaThemeScope Closes #121 --- .../querya_window_title_bar_test.dart | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 test/features/main_screen/querya_window_title_bar_test.dart diff --git a/test/features/main_screen/querya_window_title_bar_test.dart b/test/features/main_screen/querya_window_title_bar_test.dart new file mode 100644 index 00000000..835f4a69 --- /dev/null +++ b/test/features/main_screen/querya_window_title_bar_test.dart @@ -0,0 +1,101 @@ +import 'dart:ui'; + +import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:flutter/material.dart' as material; +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/theme/querya_theme.dart'; +import 'package:querya_desktop/core/theme/querya_workbench_theme.dart'; +import 'package:querya_desktop/features/main_screen/querya_window_title_bar.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; + +import '../../support/querya_theme_test_shell.dart'; + +const _customCanvas = Color(0xFF112233); +const _customSurface = Color(0xFF445566); +const _customMuted = Color(0xFF99AABB); + +QueryaTheme _themeWithWorkbench(QueryaWorkbenchTheme workbench) { + return QueryaTheme.darkDefault.copyWith(workbench: workbench); +} + +void main() { + testWidgets('title bar background follows workbench canvas', (tester) async { + late Color background; + + await tester.pumpWidget( + queryaThemeTestShell( + data: _themeWithWorkbench( + QueryaWorkbenchTheme.darkDefault.copyWith(canvas: _customCanvas), + ), + child: material.Builder( + builder: (context) { + background = QueryaWindowTitleBar.titleBarBackground(context); + return const material.SizedBox(); + }, + ), + ), + ); + + expect(background, _customCanvas); + }); + + testWidgets('window button colors use workbench surface and mutedForeground', + (tester) async { + late WindowButtonColors colors; + + await tester.pumpWidget( + queryaThemeTestShell( + data: _themeWithWorkbench( + QueryaWorkbenchTheme.darkDefault.copyWith( + surface: _customSurface, + mutedForeground: _customMuted, + ), + ), + child: material.Builder( + builder: (context) { + colors = QueryaWindowTitleBar.windowButtonColors(context); + return const material.SizedBox(); + }, + ), + ), + ); + + expect(colors.iconNormal, _customMuted); + expect(colors.mouseOver, _customSurface.withValues(alpha: 0.85)); + }); + + testWidgets('chrome style updates when QueryaThemeScope workbench changes', + (tester) async { + late Color background; + + await tester.pumpWidget( + queryaThemeTestShell( + data: _themeWithWorkbench( + QueryaWorkbenchTheme.darkDefault.copyWith(canvas: _customCanvas), + ), + child: material.Builder( + builder: (context) { + background = QueryaWindowTitleBar.titleBarBackground(context); + return const material.SizedBox(); + }, + ), + ), + ); + expect(background, _customCanvas); + + await tester.pumpWidget( + queryaThemeTestShell( + data: _themeWithWorkbench( + QueryaWorkbenchTheme.lightDefault.copyWith(canvas: _customSurface), + ), + child: material.Builder( + builder: (context) { + background = QueryaWindowTitleBar.titleBarBackground(context); + return const material.SizedBox(); + }, + ), + ), + ); + expect(background, _customSurface); + }); +} From 84a8e6acd71154053f16682c7c2037813c16a7ef Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 14 Jun 2026 13:44:43 +0300 Subject: [PATCH 3/3] fix: restore GestureDetector import and trim analyze warnings --- lib/features/main_screen/main_screen.dart | 2 +- lib/features/main_screen/querya_window_title_bar.dart | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/features/main_screen/main_screen.dart b/lib/features/main_screen/main_screen.dart index 278765c4..30bec54d 100644 --- a/lib/features/main_screen/main_screen.dart +++ b/lib/features/main_screen/main_screen.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart' as material show Scaffold, Container, - MainAxisSize, + GestureDetector, MouseRegion, SystemMouseCursors, HitTestBehavior, diff --git a/lib/features/main_screen/querya_window_title_bar.dart b/lib/features/main_screen/querya_window_title_bar.dart index ef993854..e471749a 100644 --- a/lib/features/main_screen/querya_window_title_bar.dart +++ b/lib/features/main_screen/querya_window_title_bar.dart @@ -1,7 +1,4 @@ -import 'dart:ui' show Color; - import 'package:bitsdojo_window/bitsdojo_window.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' as material show BuildContext,