make hat bar full-width, separators 1px visible
Hat bar is now a single continuous row above the three columns instead of per-column segments. Left: macOS traffic lights or empty. Center: project > branch. Right: minimize/maximize/close on Linux or empty. Column separators keep 4px hit area but show a 1px centered line. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+144
-16
@@ -1,7 +1,10 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:clide/builtin/welcome/src/welcome_view.dart';
|
||||
import 'package:clide/extension/src/contribution.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@@ -129,6 +132,7 @@ class RootLayout extends StatelessWidget {
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_HatBar(kernel: kernel),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -141,10 +145,7 @@ class RootLayout extends StatelessWidget {
|
||||
else if (sidebarVisible) ...[
|
||||
SizedBox(
|
||||
width: sidebarSize,
|
||||
child: Column(children: [
|
||||
ColumnHat.left(windowControls: kernel.window),
|
||||
Expanded(child: SlotHost(slot: Slots.sidebar)),
|
||||
]),
|
||||
child: SlotHost(slot: Slots.sidebar),
|
||||
),
|
||||
DragResizeHandle(
|
||||
arrangement: a,
|
||||
@@ -152,14 +153,7 @@ class RootLayout extends StatelessWidget {
|
||||
axis: Axis.horizontal,
|
||||
),
|
||||
],
|
||||
Expanded(child: Column(children: [
|
||||
ColumnHat.center(
|
||||
windowControls: kernel.window,
|
||||
project: kernel.project.current?.path.split('/').last,
|
||||
branch: null,
|
||||
),
|
||||
const Expanded(child: SlotHost(slot: Slots.workspace)),
|
||||
])),
|
||||
const Expanded(child: SlotHost(slot: Slots.workspace)),
|
||||
if (contextVisible && contextCollapsed)
|
||||
ClideSpine(
|
||||
label: 'context',
|
||||
@@ -174,10 +168,7 @@ class RootLayout extends StatelessWidget {
|
||||
),
|
||||
SizedBox(
|
||||
width: contextSize,
|
||||
child: Column(children: [
|
||||
ColumnHat.right(windowControls: kernel.window),
|
||||
Expanded(child: SlotHost(slot: Slots.contextPanel)),
|
||||
]),
|
||||
child: SlotHost(slot: Slots.contextPanel),
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -219,6 +210,143 @@ class RootLayout extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _HatBar extends StatelessWidget {
|
||||
const _HatBar({required this.kernel});
|
||||
final KernelServices kernel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideTheme.of(context).surface;
|
||||
return GestureDetector(
|
||||
onPanStart: (_) => kernel.window.startDrag(),
|
||||
child: Container(
|
||||
height: hatHeight,
|
||||
color: tokens.panelHeader,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
_LeftHatContent(tokens: tokens, wc: kernel.window),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ListenableBuilder(
|
||||
listenable: kernel.project,
|
||||
builder: (ctx, _) {
|
||||
final name = kernel.project.current?.path.split('/').last;
|
||||
return ClideText(
|
||||
name != null ? 'clide > $name' : 'clide',
|
||||
fontSize: 12,
|
||||
color: tokens.globalTextMuted,
|
||||
fontFamily: clideMonoFamily,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
_RightHatContent(tokens: tokens, wc: kernel.window),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LeftHatContent extends StatelessWidget {
|
||||
const _LeftHatContent({required this.tokens, required this.wc});
|
||||
final SurfaceTokens tokens;
|
||||
final WindowControls wc;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (kIsWeb) return const SizedBox.shrink();
|
||||
if (!kIsWeb && Platform.isMacOS) {
|
||||
return Row(children: [
|
||||
_TrafficDot(color: const Color(0xFFFF5F57), onTap: wc.close),
|
||||
const SizedBox(width: 6),
|
||||
_TrafficDot(color: const Color(0xFFFEBC2E), onTap: wc.minimize),
|
||||
const SizedBox(width: 6),
|
||||
_TrafficDot(color: const Color(0xFF28C840), onTap: wc.toggleMaximize),
|
||||
]);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
class _RightHatContent extends StatelessWidget {
|
||||
const _RightHatContent({required this.tokens, required this.wc});
|
||||
final SurfaceTokens tokens;
|
||||
final WindowControls wc;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (kIsWeb) return const SizedBox.shrink();
|
||||
if (!kIsWeb && Platform.isMacOS) return const SizedBox.shrink();
|
||||
return Row(children: [
|
||||
_WinBtn(icon: const PhosphorIconPainter(0xe32a), onTap: wc.minimize, tokens: tokens),
|
||||
_WinBtn(icon: const PhosphorIconPainter(0xe45e), onTap: wc.toggleMaximize, tokens: tokens),
|
||||
_WinBtn(icon: PhosphorIcons.xMark, onTap: wc.close, tokens: tokens, isClose: true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class _TrafficDot extends StatefulWidget {
|
||||
const _TrafficDot({required this.color, required this.onTap});
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
@override
|
||||
State<_TrafficDot> createState() => _TrafficDotState();
|
||||
}
|
||||
|
||||
class _TrafficDotState extends State<_TrafficDot> {
|
||||
bool _hover = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _hover = true),
|
||||
onExit: (_) => setState(() => _hover = false),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
width: 12, height: 12,
|
||||
decoration: BoxDecoration(color: _hover ? widget.color : widget.color.withAlpha(0xCC), shape: BoxShape.circle),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WinBtn extends StatefulWidget {
|
||||
const _WinBtn({required this.icon, required this.onTap, required this.tokens, this.isClose = false});
|
||||
final ClideIconPainter icon;
|
||||
final VoidCallback onTap;
|
||||
final SurfaceTokens tokens;
|
||||
final bool isClose;
|
||||
@override
|
||||
State<_WinBtn> createState() => _WinBtnState();
|
||||
}
|
||||
|
||||
class _WinBtnState extends State<_WinBtn> {
|
||||
bool _hover = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hoverBg = widget.isClose ? const Color(0xFFE81123) : widget.tokens.listItemHoverBackground;
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _hover = true),
|
||||
onExit: (_) => setState(() => _hover = false),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
width: 36, height: hatHeight,
|
||||
color: _hover ? hoverBg : null,
|
||||
alignment: Alignment.center,
|
||||
child: ClideIcon(widget.icon, size: 14, color: _hover && widget.isClose ? const Color(0xFFFFFFFF) : widget.tokens.globalTextMuted),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SlotHost extends StatelessWidget {
|
||||
const SlotHost({super.key, required this.slot});
|
||||
final SlotId slot;
|
||||
|
||||
@@ -44,10 +44,16 @@ class _DragResizeHandleState extends State<DragResizeHandle> {
|
||||
onPointerDown: _onDown,
|
||||
onPointerMove: _onMove,
|
||||
onPointerUp: _onUp,
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
width: widget.axis == Axis.horizontal ? widget.thickness : null,
|
||||
height: widget.axis == Axis.vertical ? widget.thickness : null,
|
||||
color: color,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: widget.axis == Axis.horizontal ? 1 : null,
|
||||
height: widget.axis == Axis.vertical ? 1 : null,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -25,11 +25,12 @@ static void my_application_activate(GApplication* application) {
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
|
||||
// D-057: frameless custom chrome. The Flutter app draws its own
|
||||
// per-column hats with drag regions and window buttons.
|
||||
// D-057: frameless custom chrome — the Flutter app draws its own
|
||||
// hat bar with drag regions and window buttons.
|
||||
gtk_window_set_decorated(window, FALSE);
|
||||
gtk_window_set_title(window, "clide");
|
||||
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
|
||||
// Window icon — load from the bundled asset.
|
||||
|
||||
Reference in New Issue
Block a user