feat(ui): chromeless three-island shell
This commit is contained in:
@@ -22,7 +22,7 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindow
|
desktop.MainWindow = new MainWindow
|
||||||
{
|
{
|
||||||
DataContext = Services.GetRequiredService<MainWindowViewModel>(),
|
DataContext = Services.GetRequiredService<IslandsShellViewModel>(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
Normal file
8
src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||||
|
x:Class="ClaudeDo.Ui.Views.Islands.DetailsIslandView"
|
||||||
|
x:DataType="vm:DetailsIslandViewModel">
|
||||||
|
<TextBlock Margin="14" Text="Details (placeholder)"
|
||||||
|
Foreground="{DynamicResource TextDimBrush}"/>
|
||||||
|
</UserControl>
|
||||||
8
src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs
Normal file
8
src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Views.Islands;
|
||||||
|
|
||||||
|
public partial class DetailsIslandView : UserControl
|
||||||
|
{
|
||||||
|
public DetailsIslandView() { InitializeComponent(); }
|
||||||
|
}
|
||||||
8
src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml
Normal file
8
src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||||
|
x:Class="ClaudeDo.Ui.Views.Islands.ListsIslandView"
|
||||||
|
x:DataType="vm:ListsIslandViewModel">
|
||||||
|
<TextBlock Margin="14" Text="Lists (placeholder)"
|
||||||
|
Foreground="{DynamicResource TextDimBrush}"/>
|
||||||
|
</UserControl>
|
||||||
8
src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs
Normal file
8
src/ClaudeDo.Ui/Views/Islands/ListsIslandView.axaml.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Views.Islands;
|
||||||
|
|
||||||
|
public partial class ListsIslandView : UserControl
|
||||||
|
{
|
||||||
|
public ListsIslandView() { InitializeComponent(); }
|
||||||
|
}
|
||||||
8
src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml
Normal file
8
src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||||
|
x:Class="ClaudeDo.Ui.Views.Islands.TasksIslandView"
|
||||||
|
x:DataType="vm:TasksIslandViewModel">
|
||||||
|
<TextBlock Margin="14" Text="Tasks (placeholder)"
|
||||||
|
Foreground="{DynamicResource TextDimBrush}"/>
|
||||||
|
</UserControl>
|
||||||
8
src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml.cs
Normal file
8
src/ClaudeDo.Ui/Views/Islands/TasksIslandView.axaml.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Views.Islands;
|
||||||
|
|
||||||
|
public partial class TasksIslandView : UserControl
|
||||||
|
{
|
||||||
|
public TasksIslandView() { InitializeComponent(); }
|
||||||
|
}
|
||||||
@@ -1,104 +1,42 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels"
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels"
|
||||||
xmlns:v="using:ClaudeDo.Ui.Views"
|
xmlns:islands="using:ClaudeDo.Ui.Views.Islands"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="700"
|
|
||||||
x:Class="ClaudeDo.Ui.Views.MainWindow"
|
x:Class="ClaudeDo.Ui.Views.MainWindow"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:IslandsShellViewModel"
|
||||||
Title="ClaudeDo"
|
Title="ClaudeDo"
|
||||||
Icon="avares://ClaudeDo.App/Assets/ClaudeTask.ico"
|
Width="1280" Height="820" MinWidth="780" MinHeight="600"
|
||||||
MinWidth="800" MinHeight="500"
|
Background="{DynamicResource VoidBrush}"
|
||||||
KeyDown="OnGlobalKeyDown">
|
SystemDecorations="None"
|
||||||
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
<DockPanel Background="{StaticResource WindowBgBrush}">
|
ExtendClientAreaTitleBarHeightHint="-1">
|
||||||
<v:StatusBarView DockPanel.Dock="Bottom" DataContext="{Binding StatusBar}" />
|
<Grid RowDefinitions="36,*">
|
||||||
|
<!-- Custom title bar -->
|
||||||
<Grid ColumnDefinitions="1*,2*,1.5*" Margin="8,8,8,0">
|
<Border Grid.Row="0" Background="{DynamicResource DeepBrush}" PointerPressed="OnTitleBarPressed">
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<!-- Lists island -->
|
<TextBlock Grid.Column="0" Margin="14,0" VerticalAlignment="Center"
|
||||||
<Border Grid.Column="0" CornerRadius="12" Background="{StaticResource IslandBgBrush}"
|
FontFamily="{DynamicResource SansFamily}" FontSize="12"
|
||||||
MinWidth="180" Margin="0,0,4,8" ClipToBounds="True">
|
Foreground="{DynamicResource TextDimBrush}" Text="ClaudeDo"/>
|
||||||
<DockPanel>
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="0">
|
||||||
<TextBlock DockPanel.Dock="Top"
|
<Button Classes="title-btn" Click="OnMinimize" Content="—"/>
|
||||||
Text="Lists" FontWeight="SemiBold" FontSize="13"
|
<Button Classes="title-btn" Click="OnToggleMax" Content="▢"/>
|
||||||
Foreground="{StaticResource TextSecondaryBrush}"
|
<Button Classes="title-btn close" Click="OnClose" Content="✕"/>
|
||||||
Margin="16,14,16,10"/>
|
|
||||||
|
|
||||||
<Border DockPanel.Dock="Bottom" Padding="8,8"
|
|
||||||
BorderThickness="0,1,0,0" BorderBrush="{StaticResource BorderSubtleBrush}">
|
|
||||||
<Button Content="+ New List"
|
|
||||||
Command="{Binding AddListCommand}"
|
|
||||||
Background="Transparent"
|
|
||||||
Foreground="{StaticResource AccentBrush}"
|
|
||||||
BorderThickness="0"
|
|
||||||
Padding="12,8"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
HorizontalContentAlignment="Left"
|
|
||||||
FontSize="13"
|
|
||||||
Cursor="Hand"/>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<ListBox x:Name="ListsBox"
|
|
||||||
ItemsSource="{Binding Lists}"
|
|
||||||
SelectedItem="{Binding SelectedList}"
|
|
||||||
Background="Transparent"
|
|
||||||
Margin="4,0">
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="vm:ListItemViewModel">
|
|
||||||
<Grid ColumnDefinitions="Auto,*" Margin="8,6"
|
|
||||||
Background="Transparent"
|
|
||||||
DoubleTapped="OnListItemDoubleTapped"
|
|
||||||
PointerPressed="OnListItemPointerPressed">
|
|
||||||
<Grid.ContextFlyout>
|
|
||||||
<MenuFlyout>
|
|
||||||
<MenuItem Header="Edit"
|
|
||||||
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).EditListCommand}"/>
|
|
||||||
<MenuItem Header="Delete"
|
|
||||||
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DeleteListCommand}"/>
|
|
||||||
<Separator/>
|
|
||||||
<MenuItem Header="New Task"
|
|
||||||
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).TaskList.AddTaskCommand}"/>
|
|
||||||
</MenuFlyout>
|
|
||||||
</Grid.ContextFlyout>
|
|
||||||
|
|
||||||
<Ellipse Grid.Column="0" Width="8" Height="8"
|
|
||||||
Fill="{Binding DotBrush}"
|
|
||||||
VerticalAlignment="Center" Margin="0,0,10,0"/>
|
|
||||||
|
|
||||||
<StackPanel Grid.Column="1">
|
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="Medium"
|
|
||||||
Foreground="{StaticResource TextSecondaryBrush}"/>
|
|
||||||
<TextBlock Text="{Binding WorkingDir}" FontSize="10"
|
|
||||||
Foreground="{StaticResource TextDimBrush}"
|
|
||||||
IsVisible="{Binding WorkingDir, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
</DockPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Tasks island -->
|
<!-- Three islands -->
|
||||||
<Border Grid.Column="1" CornerRadius="12" Background="{StaticResource IslandBgBrush}"
|
<Grid Grid.Row="1" Margin="7" ColumnDefinitions="260,*,320">
|
||||||
Margin="4,0,4,8" ClipToBounds="True">
|
<Border Grid.Column="0" Classes="island" Margin="7">
|
||||||
<DockPanel>
|
<islands:ListsIslandView DataContext="{Binding Lists}"/>
|
||||||
<TextBlock DockPanel.Dock="Top"
|
|
||||||
Text="{Binding TaskList.ListName, FallbackValue='Tasks'}"
|
|
||||||
FontWeight="SemiBold" FontSize="16"
|
|
||||||
Foreground="{StaticResource TextPrimaryBrush}"
|
|
||||||
Margin="16,14,16,10"/>
|
|
||||||
<v:TaskListView DataContext="{Binding TaskList}" />
|
|
||||||
</DockPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
|
<Border Grid.Column="1" Classes="island" Margin="7">
|
||||||
<!-- Detail island -->
|
<islands:TasksIslandView DataContext="{Binding Tasks}"/>
|
||||||
<Border Grid.Column="2" CornerRadius="12" Background="{StaticResource IslandBgBrush}"
|
</Border>
|
||||||
MinWidth="280" Margin="4,0,0,8" ClipToBounds="True">
|
<Border Grid.Column="2" Classes="island" Margin="7"
|
||||||
<v:TaskDetailView DataContext="{Binding TaskDetail}" />
|
IsVisible="{Binding ShowDetails}">
|
||||||
|
<islands:DetailsIslandView DataContext="{Binding Details}"/>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,75 +1,27 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using ClaudeDo.Ui.ViewModels;
|
using ClaudeDo.Ui.ViewModels;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Views;
|
namespace ClaudeDo.Ui.Views;
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
public MainWindow()
|
public MainWindow() { InitializeComponent(); }
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async void OnOpened(EventArgs e)
|
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnOpened(e);
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
if (DataContext is MainWindowViewModel vm)
|
BeginMoveDrag(e);
|
||||||
await vm.InitializeAsync();
|
|
||||||
}
|
}
|
||||||
|
private void OnMinimize(object? s, Avalonia.Interactivity.RoutedEventArgs e) =>
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
private void OnToggleMax(object? s, Avalonia.Interactivity.RoutedEventArgs e) =>
|
||||||
|
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||||
|
private void OnClose(object? s, Avalonia.Interactivity.RoutedEventArgs e) => Close();
|
||||||
|
|
||||||
private void OnGlobalKeyDown(object? sender, KeyEventArgs e)
|
protected override void OnSizeChanged(SizeChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not MainWindowViewModel vm) return;
|
base.OnSizeChanged(e);
|
||||||
|
if (DataContext is IslandsShellViewModel vm) vm.WindowWidth = Bounds.Width;
|
||||||
var ctrl = e.KeyModifiers.HasFlag(KeyModifiers.Control);
|
|
||||||
var shift = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
|
|
||||||
|
|
||||||
if (ctrl && shift && e.Key == Key.N)
|
|
||||||
{
|
|
||||||
vm.AddListCommand.Execute(null);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (ctrl && e.Key == Key.N)
|
|
||||||
{
|
|
||||||
var taskListView = this.GetVisualDescendants().OfType<TaskListView>().FirstOrDefault();
|
|
||||||
taskListView?.FocusInlineAdd();
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (ctrl && e.Key == Key.L)
|
|
||||||
{
|
|
||||||
this.FindControl<ListBox>("ListsBox")?.Focus();
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (ctrl && e.Key == Key.R)
|
|
||||||
{
|
|
||||||
if (vm.TaskList.SelectedTask is { } task)
|
|
||||||
{
|
|
||||||
task.RunNowCommand.Execute(null);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnListItemDoubleTapped(object? sender, TappedEventArgs e)
|
|
||||||
{
|
|
||||||
if (DataContext is MainWindowViewModel vm)
|
|
||||||
vm.EditListCommand.Execute(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnListItemPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
||||||
{
|
|
||||||
var props = e.GetCurrentPoint(this).Properties;
|
|
||||||
if (!props.IsRightButtonPressed) return;
|
|
||||||
|
|
||||||
if (sender is Grid { DataContext: ListItemViewModel item }
|
|
||||||
&& DataContext is MainWindowViewModel vm)
|
|
||||||
{
|
|
||||||
vm.SelectedList = item;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user