Add project files
This commit is contained in:
commit
97cafcfa18
16 files changed
+1540
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
.vs/
|
||||||
|
.idea/
|
||||||
|
*.user
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="NexvaneHub.App"
|
||||||
|
RequestedThemeVariant="Dark">
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
<StyleInclude Source="avares://NexvaneHub/Styles/Theme.axaml" />
|
||||||
|
<StyleInclude Source="avares://NexvaneHub/Styles/Controls.axaml" />
|
||||||
|
</Application.Styles>
|
||||||
|
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using NexvaneHub.ViewModels;
|
||||||
|
using NexvaneHub.Views;
|
||||||
|
|
||||||
|
namespace NexvaneHub;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow
|
||||||
|
{
|
||||||
|
DataContext = new MainWindowViewModel(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1920.000000pt" height="1080.000000pt" viewBox="0 0 1920.000000 1080.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M5760 9083 l0 -828 825 825 c454 454 825 826 825 827 0 2 -371 3
|
||||||
|
-825 3 l-825 0 0 -827z"/>
|
||||||
|
<path d="M12777 8942 l-967 -967 0 -1323 c0 -1329 4 -1597 40 -2406 11 -242
|
||||||
|
18 -442 16 -444 -7 -6 199 -360 -2594 4458 -349 602 -650 1121 -670 1154 l-35
|
||||||
|
58 -1403 -1403 -1404 -1404 0 -2820 0 -2820 970 970 970 970 0 1265 c0 696 -5
|
||||||
|
1384 -10 1530 -12 315 -36 819 -54 1125 -8 121 -12 221 -11 223 4 5 -45 89
|
||||||
|
1615 -2798 865 -1504 1590 -2766 1612 -2804 l40 -69 1434 1434 1434 1434 0
|
||||||
|
2802 c0 1542 -3 2803 -8 2803 -4 0 -442 -435 -975 -968z"/>
|
||||||
|
<path d="M12855 1820 l-900 -900 903 0 902 0 0 900 c0 495 -1 900 -3 900 -1 0
|
||||||
|
-407 -405 -902 -900z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
|
namespace NexvaneHub.Converters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares a bound enum value's string representation against ConverterParameter.
|
||||||
|
/// Used for "is this nav item selected" / "is this section visible" bindings.
|
||||||
|
/// </summary>
|
||||||
|
public class EnumEqualsConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public static readonly EnumEqualsConverter Instance = new();
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is null || parameter is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(value.ToString(), parameter.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NexvaneHub.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plain data describing a project entry. Kept free of INotifyPropertyChanged
|
||||||
|
/// on purpose - ProjectItemViewModel wraps this for anything the UI binds to,
|
||||||
|
/// and writes edits back onto this instance.
|
||||||
|
/// </summary>
|
||||||
|
public class ProjectModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DateTime LastModified { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Not user-editable in the UI - tied to where the project lives on disk.</summary>
|
||||||
|
public string Directory { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string ThumbnailPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string ProjectVersion { get; set; } = "1.0.0";
|
||||||
|
|
||||||
|
public string SdkVersion { get; set; } = "1.4.2";
|
||||||
|
|
||||||
|
public string DefaultCompiler { get; set; } = "MSVC";
|
||||||
|
|
||||||
|
public string DefaultArchitecture { get; set; } = "Auto";
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
<RootNamespace>NexvaneHub</RootNamespace>
|
||||||
|
<AssemblyName>NexvaneHub</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.1.3" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
|
||||||
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.3" />
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NexvaneHub;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||||
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||||
|
// yet and stuff might break.
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace();
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
# Nexvane Hub
|
||||||
|
|
||||||
|
An Avalonia UI (MVVM) scaffold for a project-launcher "hub" app, styled to match
|
||||||
|
the Nexvane editor's dark UI and the cards/buttons from the marketing site.
|
||||||
|
|
||||||
|
## Running it
|
||||||
|
|
||||||
|
Requires the .NET 8 SDK.
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet restore
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
NexvaneHub/
|
||||||
|
├─ Models/
|
||||||
|
│ └─ ProjectModel.cs Plain data: name, directory, SDK version, etc.
|
||||||
|
├─ ViewModels/
|
||||||
|
│ ├─ ViewModelBase.cs
|
||||||
|
│ ├─ MainWindowViewModel.cs Nav state, project list, Open/Delete/New commands
|
||||||
|
│ └─ ProjectItemViewModel.cs
|
||||||
|
├─ Views/
|
||||||
|
│ └─ MainWindow.axaml(.cs) Top bar + sidebar nav + card list + detail panel
|
||||||
|
├─ Converters/
|
||||||
|
│ └─ EnumEqualsConverter.cs Drives nav selection & section switching
|
||||||
|
├─ Styles/
|
||||||
|
│ ├─ Theme.axaml Color palette (brushes) — edit this to re-theme
|
||||||
|
│ └─ Controls.axaml Card / button / nav-item / field styles
|
||||||
|
└─ App.axaml(.cs)
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's wired up vs. stubbed
|
||||||
|
|
||||||
|
- The **Projects** section is fully built: card list, selection, detail panel
|
||||||
|
with collapsible "Project" / "SDK" groups, New / Open / Delete.
|
||||||
|
- Every field in the detail panel is a live, two-way-bound `TextBox` —
|
||||||
|
**Name** (the title), **Thumbnail**, **Version**, **SDK Version**,
|
||||||
|
**Default Compiler**, **Default Arch** — and edits write straight back onto
|
||||||
|
the underlying `ProjectModel` via the `On<X>Changed` partial methods in
|
||||||
|
`ProjectItemViewModel.cs`. **Directory** is the one exception: it's a plain
|
||||||
|
read-only field (not a `TextBox`), since it's tied to where the project
|
||||||
|
actually lives on disk rather than something you'd retype by hand.
|
||||||
|
- Default Compiler / Default Arch are plain text inputs here rather than
|
||||||
|
dropdowns of preset values (MSVC/Clang/GCC, x64/ARM64/Auto) — say the word
|
||||||
|
and I'll swap them for `ComboBox`es with those options.
|
||||||
|
- **SDK** and **Packages** nav items switch the middle pane but are placeholder
|
||||||
|
content — swap the placeholder `Border` in `MainWindow.axaml` for real views
|
||||||
|
when you're ready (a `SdkView.axaml` / `PackagesView.axaml` user control each,
|
||||||
|
same pattern as the project list).
|
||||||
|
- `OpenProjectCommand` and `DeleteProjectCommand` are stubbed in
|
||||||
|
`MainWindowViewModel` — `DeleteProject` already removes it from the in-memory
|
||||||
|
list, but neither touches disk. Hook `OpenProject` up to launching your editor
|
||||||
|
process against `SelectedProject.Directory`, and add a confirmation dialog to
|
||||||
|
`DeleteProject` before it goes near real files.
|
||||||
|
- Project thumbnails are plain placeholder tiles (no image loading yet) since
|
||||||
|
there's no asset pipeline in this scaffold — swap `Border.thumb` /
|
||||||
|
`Border.thumbLarge` for an `Image` bound to `ThumbnailPath` once you have
|
||||||
|
real screenshots to show.
|
||||||
|
|
||||||
|
## Custom title bar
|
||||||
|
|
||||||
|
The window uses a fully custom title bar (`ExtendClientAreaToDecorationsHint`
|
||||||
|
+ `ExtendClientAreaChromeHints="NoChrome"` in `MainWindow.axaml`), so the OS's
|
||||||
|
native title/caption buttons are gone and replaced with themed ones. Two
|
||||||
|
things make this work, both in `MainWindow.axaml.cs`:
|
||||||
|
|
||||||
|
- Dragging: with no native chrome, the OS no longer treats the top bar as
|
||||||
|
draggable, so `TitleBar_PointerPressed` calls `BeginMoveDrag` by hand. The
|
||||||
|
settings gear and the minimize/maximize/close buttons sit inside that same
|
||||||
|
bar but consume their own `PointerPressed` before it bubbles up, so
|
||||||
|
clicking them doesn't also start a drag - you don't need to carve out
|
||||||
|
separate "drag zones" around them.
|
||||||
|
- Minimize/maximize/close: plain `Click` handlers that flip `WindowState`
|
||||||
|
or call `Close()`.
|
||||||
|
|
||||||
|
This is the standard cross-platform-friendly approach (as opposed to
|
||||||
|
`SystemDecorations="None"`, which also removes OS-level resize/snap
|
||||||
|
behavior) but it's only been checked against the Windows screenshots you
|
||||||
|
sent — Linux/macOS may need small adjustments (e.g. macOS traffic-light
|
||||||
|
buttons behave differently under this hint).
|
||||||
|
|
||||||
|
## Design system
|
||||||
|
|
||||||
|
- **Rounded corners (6-8px) almost everywhere** — buttons, fields, cards, the
|
||||||
|
project list frame, the logo mark. The one exception is the **left sidebar
|
||||||
|
buttons** (Projects/SDK/Packages/Account), which stay square and flush
|
||||||
|
against each other, no radius, no margin — everything else softened.
|
||||||
|
- **Flush pairs where it matters:** sidebar nav items sit directly against
|
||||||
|
each other, and Open/Delete are one visual pair with no gap between them —
|
||||||
|
Open's own bottom border doubles as the divider, and only the outer
|
||||||
|
corners of the pair are rounded (`CornerRadius="6,6,0,0"` / `"0,0,6,6"`).
|
||||||
|
- **Accent blue appears only at the instant of interaction** — a button
|
||||||
|
being pressed, or a field gaining focus. Nothing stays accent-colored at
|
||||||
|
rest: the active sidebar tab is a neutral highlight + bold text, and the
|
||||||
|
Project/SDK section headers are neutral even while expanded. That second
|
||||||
|
one needed an explicit override — Avalonia's `ToggleButton` has a
|
||||||
|
built-in accent-colored `:checked` style from the Fluent theme, so
|
||||||
|
`Controls.axaml` overrides `ToggleButton.sectionHeader:checked` directly;
|
||||||
|
without that override the section headers would stay blue whenever
|
||||||
|
expanded regardless of anything else in the stylesheet. Delete keeps its
|
||||||
|
own red language on press rather than switching to blue, since a
|
||||||
|
destructive action turning "accent blue" would read as reassuring rather
|
||||||
|
than a warning.
|
||||||
|
- Palette pulled tighter to the editor screenshot: `#1B1B1D` background,
|
||||||
|
`#1F1F22` panels, `#2F6FED` accent. All of it lives in `Styles/Theme.axaml`
|
||||||
|
as named brushes — change values there and it propagates everywhere. Shapes
|
||||||
|
(radius, padding, borders) live in `Styles/Controls.axaml`.
|
||||||
|
|
||||||
|
## Layout fixes from the last round
|
||||||
|
|
||||||
|
- **Scrollbar gutter:** both scrollable areas (the project list, the detail
|
||||||
|
panel's fields) now have `Padding="0,0,14,0"` on their `ScrollViewer`, so
|
||||||
|
the scrollbar has its own space instead of drawing over the last ~14px of
|
||||||
|
content.
|
||||||
|
- **Divider vs. hover width:** `ListBoxItem` padding was moved off the item
|
||||||
|
itself and onto the content inside each row's `DataTemplate`. Previously
|
||||||
|
the item's own padding shrank the divider line but not the hover/selected
|
||||||
|
background, so the two didn't line up — now both span the row's full
|
||||||
|
width.
|
||||||
|
- **New folded into the Projects card:** the project list and the "New"
|
||||||
|
button now live inside one `Border` (rounded + `ClipToBounds="True"`),
|
||||||
|
with New as a bottom row separated only by a hairline — not a second,
|
||||||
|
separately-bordered element below the card.
|
||||||
|
- **Open/Delete got their divider back:** the fully-seamless version from
|
||||||
|
last round read as one blurred shape rather than two buttons. They're
|
||||||
|
still a single rounded, clipped block with zero margin between them, just
|
||||||
|
with a 1px hairline down the middle so the pairing is unambiguous.
|
||||||
|
- **Detail panel now belongs to the Projects tab:** the right column
|
||||||
|
(`Border.detailPanel`) is `IsVisible`-bound to `CurrentSection == Projects`
|
||||||
|
the same way the project list already was — it no longer stays on screen
|
||||||
|
showing stale project data while browsing SDK/Packages. Note the column
|
||||||
|
itself is a fixed `360` width in the outer `Grid.ColumnDefinitions`, so
|
||||||
|
when it's hidden that space goes blank rather than being reclaimed by the
|
||||||
|
middle column — say the word if you'd rather it collapse to `0` and let
|
||||||
|
the list expand to fill it.
|
||||||
|
- **Title bar menu row:** a second row (`File / Edit / View / Tools / Help`,
|
||||||
|
plain text buttons, no persistent fill) now sits below the title, per your
|
||||||
|
correction on the reference screenshot. Both rows live inside the same
|
||||||
|
draggable `Border.topBar`, so dragging still works from either row's blank
|
||||||
|
space, and the window's outer `Grid` row switched from a fixed `56` to
|
||||||
|
`Auto` to fit the taller two-row bar.
|
||||||
|
|
||||||
|
## Menu dropdowns
|
||||||
|
|
||||||
|
Each menu item opens a dropdown via `Button.Flyout` — Avalonia's built-in
|
||||||
|
mechanism handles positioning, click-away-to-close, and Escape-to-dismiss;
|
||||||
|
everything visible inside is our own styled content (`Button.dropdownItem`
|
||||||
|
rows + `Border.dropdownSeparator` dividers), not the default theme's `Menu`/
|
||||||
|
`MenuItem` look. This was a deliberate choice: `MenuItem`'s built-in submenu
|
||||||
|
popup pulls its background/border from deeper theme resources than a plain
|
||||||
|
`Button`, so styling `Button` + `Flyout` (only two things to override:
|
||||||
|
`FlyoutPresenter` for the panel chrome, `Button.dropdownItem` for the rows)
|
||||||
|
was the more reliable path without being able to compile-check the result.
|
||||||
|
|
||||||
|
- **Sections** are just `Border.dropdownSeparator` — a 1px divider — dropped
|
||||||
|
between groups of `Button.dropdownItem` rows in the `StackPanel`. Add or
|
||||||
|
remove them freely; there's no hidden logic tying them to anything.
|
||||||
|
- Every dropdown item is a placeholder with no `Click` handler, same as the
|
||||||
|
menu bar buttons themselves last round — clicking one closes the dropdown
|
||||||
|
(that part's free, from the Flyout) but doesn't do anything yet. Wiring
|
||||||
|
one up is a `Click="SomeHandler"` in the XAML plus a method in
|
||||||
|
`MainWindow.axaml.cs` (or a `Command` binding if it belongs on
|
||||||
|
`MainWindowViewModel` instead — e.g. "Toggle Sidebar" is a real candidate
|
||||||
|
for that once the sidebar is collapsible).
|
||||||
|
- The item lists (New Project, Undo, Toggle Sidebar, etc.) are my best guess
|
||||||
|
at what a project-hub menu would plausibly contain, not something you
|
||||||
|
asked for by name — trim, rename, or reorganize the sections however
|
||||||
|
actually fits.
|
||||||
|
|
||||||
|
## Logo mark
|
||||||
|
|
||||||
|
The title bar's logo badge now renders your actual `nexvane-logo.svg`
|
||||||
|
(kept in `Assets/nexvane-logo.svg` for reference) instead of a placeholder
|
||||||
|
"N" glyph, and spans the full height of both title-bar rows rather than
|
||||||
|
being confined to the top one — like the editor's title bar. Both were part
|
||||||
|
of the same request.
|
||||||
|
|
||||||
|
- **Why it's inline vector, not an `<Image>`:** Avalonia has no built-in SVG
|
||||||
|
support, and pulling in an SVG-rendering package would mean a new
|
||||||
|
dependency I couldn't verify actually resolves/compiles without a working
|
||||||
|
`dotnet restore` here. Instead, the exact path data from your SVG (all
|
||||||
|
three `<path>` elements, copied verbatim — the geometry itself isn't
|
||||||
|
something I redrew or approximated) is embedded directly as Avalonia
|
||||||
|
`Path` elements inside `MainWindow.axaml`, wrapped in a `Canvas` with the
|
||||||
|
same `scale(0.1,-0.1)` + `translate` your SVG applies, further shifted so
|
||||||
|
the mark's tight bounding box starts at `(0,0)` and a `Viewbox` scales it
|
||||||
|
cleanly to size. Net effect: what renders is your logo's real geometry,
|
||||||
|
using only built-in Avalonia controls — no new package, so nothing new
|
||||||
|
that can fail to restore.
|
||||||
|
- **The tall badge:** `Border.logoBadge` changed from a fixed `30x30` square
|
||||||
|
to a `64`-wide column that stretches to fill its `Grid` cell's height —
|
||||||
|
paired with moving it into its own `Grid.Column="0"` next to a
|
||||||
|
`Grid.Column="1"` holding both the title row and the menu row stacked
|
||||||
|
on top of each other, so the badge naturally spans both. `Border.topBar`'s
|
||||||
|
own padding moved from the whole bar onto just that second column
|
||||||
|
(`Margin="16,0,20,0"`), since the badge needs to sit flush in the corner,
|
||||||
|
not inset.
|
||||||
|
- If you'd rather use the actual `.svg` file directly (say, for reuse
|
||||||
|
elsewhere as an app icon) rather than this inlined copy, that's where
|
||||||
|
a package like `Avalonia.Svg.Skia` would come in — just flag it and I'll
|
||||||
|
wire it up, now that there's a known-working fallback already in place if
|
||||||
|
that package route hits a snag.
|
||||||
|
|
||||||
|
## Title bar height fix
|
||||||
|
|
||||||
|
The oversized bar (and the stray unstyled "Close" button floating below the
|
||||||
|
real controls) both trace back to the same thing: the bar's total height was
|
||||||
|
being **auto-measured** through several nested layers — `Viewbox` inside
|
||||||
|
`Border` inside `Grid` inside `StackPanel` inside `Border.topBar` — while
|
||||||
|
`ExtendClientAreaTitleBarHeightHint="-1"` told the OS to *auto-detect* that
|
||||||
|
same height from the content. Two auto-measurements that both depend on
|
||||||
|
several nested layers agreeing exactly are exactly the kind of thing that's
|
||||||
|
fragile to get wrong, and apparently did here.
|
||||||
|
|
||||||
|
Fixed by pinning the number everywhere instead of deriving it:
|
||||||
|
|
||||||
|
- `ExtendClientAreaTitleBarHeightHint="88"` — explicit, not `-1`.
|
||||||
|
- The window's outer `Grid.RowDefinitions` is `"88,*"`, not `"Auto,*"`.
|
||||||
|
- `Border.topBar` has an explicit `Height="88"` in `Controls.axaml`.
|
||||||
|
- The menu row is `Height="32"` directly, not implied by margin/padding.
|
||||||
|
|
||||||
|
56 (title row) + 32 (menu row) = 88 everywhere, top to bottom, instead of
|
||||||
|
being something four different elements had to happen to agree on through
|
||||||
|
measurement. I couldn't run the app to confirm this eliminates the stray
|
||||||
|
"Close" button specifically — that part reads as Windows/Avalonia injecting
|
||||||
|
a fallback once the declared and auto-detected heights disagreed, which is
|
||||||
|
a reasonable explanation but not one I can verify without a build. If it's
|
||||||
|
still there after this, the next thing to try is `SystemDecorations="None"`
|
||||||
|
instead of the `ExtendClientArea*` hints — a blunter tool that drops native
|
||||||
|
resize/snap behavior, but leaves nothing for the OS to guess about.
|
||||||
|
|
||||||
|
## Logo crop + menu alignment fix
|
||||||
|
|
||||||
|
Two more from the same area, addressed separately since the title bar
|
||||||
|
height/Close-button issue above is parked for now:
|
||||||
|
|
||||||
|
- **Logo was cropping.** The previous version scaled/positioned the mark via
|
||||||
|
`Canvas.RenderTransform` (a `ScaleTransform` + `TranslateTransform`) while
|
||||||
|
leaving the raw SVG coordinates untouched, relying on `Viewbox` to
|
||||||
|
correctly reconcile the `Canvas`'s *declared* size against a *separately
|
||||||
|
transformed* child. I re-verified the bounding-box math itself was
|
||||||
|
correct (recomputed it a second way, using a full path parser rather than
|
||||||
|
just tracking points, and got the same 800×899 result) — so the transform
|
||||||
|
numbers weren't wrong, but I couldn't rule out the reconciliation itself
|
||||||
|
as the failure point without being able to render it. Removed that
|
||||||
|
uncertainty entirely: the SVG's `scale(0.1,-0.1)` + `translate` is now
|
||||||
|
baked directly into every coordinate (computed once, offline), so each
|
||||||
|
`Path.Data` is already in its final coordinate space — no
|
||||||
|
`RenderTransform` left for `Viewbox` to reconcile against. There's also a
|
||||||
|
2% safety margin baked into the `Canvas` size now, so even a small
|
||||||
|
rounding difference can't clip anything.
|
||||||
|
- **Menu row wasn't aligned with the title.** `Button.menuBarItem` has
|
||||||
|
`Padding="7,3"` for a comfortable hit target, but the title `TextBlock`
|
||||||
|
above it has no padding — so "File" rendered 7px right of where "Nexvane"
|
||||||
|
starts. Fixed with `Margin="-7,0,0,0"` on the menu row's `StackPanel`,
|
||||||
|
canceling that offset out so the two rows' text lines up.
|
||||||
|
|
||||||
|
## Compacting the title bar
|
||||||
|
|
||||||
|
The 88px bar had both rows *centered inside* fixed heights bigger than
|
||||||
|
their actual content needed, which read as loose/padded rather than tight
|
||||||
|
like the editor reference. Shrunk everything to hug its content instead of
|
||||||
|
floating inside oversized rows, and brought the bar's total height down to
|
||||||
|
match:
|
||||||
|
|
||||||
|
- Title row: `56px → 38px`. Menu row: `32px → 24px`. Total: `88px → 62px`
|
||||||
|
(same pattern as before — pinned as an explicit number in four places:
|
||||||
|
`ExtendClientAreaTitleBarHeightHint`, the window's outer `Grid` row,
|
||||||
|
`Border.topBar`'s `Height`, and the two rows' own heights summing to it).
|
||||||
|
- Caption buttons (`38×26`, down from `44×32`), the settings gear
|
||||||
|
(`26×26`, down from `34×34`), and the logo `Viewbox` (`22×24.6`, down
|
||||||
|
from `26×29`) all shrank to match, so nothing looks oversized next to the
|
||||||
|
tighter text.
|
||||||
|
- `Button.menuBarItem`'s padding went from `7,3` to `6,2`, and the
|
||||||
|
alignment-compensating margin on the menu row updated from `-7` to `-6`
|
||||||
|
to match.
|
||||||
|
- `Border.logoBadge` narrowed from `64px` to `56px` wide, in proportion
|
||||||
|
with the shorter bar.
|
||||||
|
|
||||||
|
All of this is proportional scaling from the same values as before, not a
|
||||||
|
redesign — if 62px is still taller (or shorter) than you'd like, every
|
||||||
|
number above is a single line in `Controls.axaml` or `MainWindow.axaml`,
|
||||||
|
not something that requires touching the structure.
|
||||||
|
|
||||||
|
## Fixing the logo/text vertical balance
|
||||||
|
|
||||||
|
Two related issues after compacting: the text rows visually overshooting
|
||||||
|
the logo (top and bottom), and everything sitting too close to the bar's
|
||||||
|
edges. Same root cause for both — the logo's `Viewbox` (`22×24.6`) was
|
||||||
|
small relative to its `62px`-tall column, so it rendered as a small mark
|
||||||
|
centered in a lot of invisible padding, while the title/menu rows spanned
|
||||||
|
the *full* column height with only their own font-metric spacing. The
|
||||||
|
logo's visible ink and the text's visible extent weren't bounded by the
|
||||||
|
same space, even though they shared the same container.
|
||||||
|
|
||||||
|
Fixed by giving both the same physical space to live in:
|
||||||
|
|
||||||
|
- `Border.topBar` now has `Padding="0,3,0,3"` — a shared 3px inset from the
|
||||||
|
bar's top and bottom that both the logo *and* the text column sit inside
|
||||||
|
(it's on the shared parent, not applied separately to each side, so
|
||||||
|
there's no way for them to drift out of sync again). Total height is
|
||||||
|
still `62px`; the title/menu rows shrank from `38/24` to `34/22` to fit
|
||||||
|
inside the now-smaller `56px` content area (`34+22=56`, `+3+3` padding
|
||||||
|
`=62`).
|
||||||
|
- The logo `Viewbox` grew from `22×24.6` to `43×48` — closer to filling
|
||||||
|
that `56px` content height instead of floating small in the middle, so
|
||||||
|
its visible bounds now roughly bracket the text block instead of being
|
||||||
|
dwarfed by it. `Border.logoBadge` widened from `56px` to `60px` to give
|
||||||
|
the bigger mark reasonable side clearance.
|
||||||
|
- The text column's margins grew from `14,0,16,0` to `20,0,20,0` for more
|
||||||
|
breathing room from both the logo and the window's right edge.
|
||||||
@@ -0,0 +1,419 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Design rules for this pass:
|
||||||
|
- Corners get a modest radius (6-8px) everywhere EXCEPT the left sidebar
|
||||||
|
buttons (nav items, Account), which stay square/flush.
|
||||||
|
- Related buttons/rows still sit flush against each other where that
|
||||||
|
made sense (project rows, the Open/Delete pair) - separated by
|
||||||
|
hairlines, not gaps.
|
||||||
|
- Accent blue appears ONLY the instant something is pressed or a field
|
||||||
|
is focused - never as a resting "this is currently selected/expanded"
|
||||||
|
color. Selected/expanded states use neutral gray + bold text instead.
|
||||||
|
Note: ToggleButton has a built-in accent-colored ":checked" style from
|
||||||
|
the Fluent theme, so it's explicitly overridden below - without that
|
||||||
|
override the Project/SDK section headers would stay blue whenever
|
||||||
|
expanded, regardless of anything set on the base selector.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- ===================== Top bar ===================== -->
|
||||||
|
<Style Selector="Border.topBar">
|
||||||
|
<Setter Property="Height" Value="62" />
|
||||||
|
<Setter Property="Padding" Value="0,3,0,3" />
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DividerBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Flush block spanning the full height of both title-bar rows, like
|
||||||
|
the editor's logo mark - not padded/inset, sits right in the corner.
|
||||||
|
No background fill - just the mark itself on the bar. -->
|
||||||
|
<Style Selector="Border.logoBadge">
|
||||||
|
<Setter Property="Width" Value="60" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.appTitle">
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.appTitleMuted">
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||||
|
<Setter Property="Margin" Value="6,0,0,0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.iconButton">
|
||||||
|
<Setter Property="Width" Value="26" />
|
||||||
|
<Setter Property="Height" Value="26" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.iconButton:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.iconButton:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Custom window caption buttons (minimize/maximize/close), replacing
|
||||||
|
the native ones now that the title bar is fully custom-drawn. Square
|
||||||
|
on purpose - this mimics standard OS caption buttons, which are
|
||||||
|
always flush rectangles regardless of the rest of the theme. -->
|
||||||
|
<Style Selector="Button.captionButton">
|
||||||
|
<Setter Property="Width" Value="38" />
|
||||||
|
<Setter Property="Height" Value="26" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="10" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.captionButton:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.captionButton:pointerover">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.captionButton:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
<!-- Close keeps the same red language Delete uses, not accent blue. -->
|
||||||
|
<Style Selector="Button.captionButtonClose:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource DangerBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.captionButtonClose:pointerover">
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.captionButtonClose:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource DangerPressedBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Secondary menu row (File/Edit/View/Tools/Help), sitting below the
|
||||||
|
title row rather than above it. Plain text items - background only
|
||||||
|
appears on hover/press, no border, no persistent fill. -->
|
||||||
|
<Style Selector="Button.menuBarItem">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
|
<Setter Property="Padding" Value="6,2" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="12.5" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.menuBarItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.menuBarItem:pointerover">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.menuBarItem:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.menuBarItem:pressed">
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Dropdown panel opened by a menu bar item's Button.Flyout. Applies to
|
||||||
|
every Flyout in the app - fine for now since menus are the only use. -->
|
||||||
|
<Style Selector="FlyoutPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
<Setter Property="Padding" Value="6" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- One row inside a dropdown. -->
|
||||||
|
<Style Selector="Button.dropdownItem">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
|
<Setter Property="Padding" Value="10,7" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.dropdownItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.dropdownItem:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.dropdownItem:pressed">
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Divides a dropdown into sections. -->
|
||||||
|
<Style Selector="Border.dropdownSeparator">
|
||||||
|
<Setter Property="Height" Value="1" />
|
||||||
|
<Setter Property="Background" Value="{StaticResource DividerBrush}" />
|
||||||
|
<Setter Property="Margin" Value="6,4" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ===================== Sidebar navigation (stays square/flush) ===================== -->
|
||||||
|
<Style Selector="Border.sidebar">
|
||||||
|
<Setter Property="Background" Value="{StaticResource SidebarBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DividerBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,1,0" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.navItem">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Padding" Value="16,11" />
|
||||||
|
<Setter Property="Margin" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="13.5" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.navItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.navItem:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
<!-- Active tab: neutral highlight + bold text, no accent at rest. -->
|
||||||
|
<Style Selector="Button.navItem.selected /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.navItem.selected">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.accountItem">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0,1,0,0" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DividerBrush}" />
|
||||||
|
<Setter Property="Padding" Value="16,11" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.accountItem:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.accountItem:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ===================== Project list (middle column) ===================== -->
|
||||||
|
<Style Selector="Border.listContainer">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="ClipToBounds" Value="True" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="ListBox.projectList">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Rows stay flush inside the rounded container - no radius, no margin.
|
||||||
|
Padding lives on the row content (below), not here, so the hover/
|
||||||
|
selected background and the divider line both span the exact same
|
||||||
|
full-bleed width instead of one being inset from the other. -->
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="Margin" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource RowSelectedBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Border.listRow">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DividerBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Border.thumb">
|
||||||
|
<Setter Property="Width" Value="56" />
|
||||||
|
<Setter Property="Height" Value="56" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Border.thumbLarge">
|
||||||
|
<Setter Property="Height" Value="170" />
|
||||||
|
<Setter Property="CornerRadius" Value="8" />
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.cardTitle">
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.cardSubtitle">
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextMutedBrush}" />
|
||||||
|
<Setter Property="Margin" Value="0,3,0,0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ===================== Detail panel (right column) ===================== -->
|
||||||
|
<Style Selector="Border.detailPanel">
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DividerBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1,0,0,0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.fieldLabel">
|
||||||
|
<Setter Property="FontSize" Value="12.5" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Read-only display (Directory only) - a plain box, not an input. -->
|
||||||
|
<Style Selector="Border.fieldBox">
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Padding" Value="10,7" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.fieldValueMuted">
|
||||||
|
<Setter Property="FontSize" Value="12.5" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextMutedBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Editable fields (everything except Directory). -->
|
||||||
|
<Style Selector="TextBox.input">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Padding" Value="10,7" />
|
||||||
|
<Setter Property="FontSize" Value="12.5" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
<Setter Property="CaretBrush" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
<Setter Property="SelectionBrush" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox.input:pointerover">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
</Style>
|
||||||
|
<!-- Focusing an input is the field-level equivalent of "clicked". -->
|
||||||
|
<Style Selector="TextBox.input:focus">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox.titleInput">
|
||||||
|
<Setter Property="FontSize" Value="17" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="ToggleButton.sectionHeader">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Padding" Value="10,8" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
</Style>
|
||||||
|
<!-- Override the Fluent theme's default accent-colored ":checked" look -
|
||||||
|
expanded is a state, not a click, so it stays neutral. -->
|
||||||
|
<Style Selector="ToggleButton.sectionHeader:checked /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ToggleButton.sectionHeader:checked">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ToggleButton.sectionHeader:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ===================== Buttons ===================== -->
|
||||||
|
<Style Selector="Button.primary">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Padding" Value="14,10" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.primary:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource RowSelectedBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.primary:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.primary:disabled /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.danger">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource DangerBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DangerBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Padding" Value="14,9" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.danger:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#2A1416" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.danger:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource DangerPressedBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.danger:disabled /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.newProject">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource CardBorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="CornerRadius" Value="6" />
|
||||||
|
<Setter Property="Padding" Value="14,12" />
|
||||||
|
<Setter Property="FontSize" Value="13.5" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.newProject:pointerover">
|
||||||
|
<Setter Property="Background" Value="{StaticResource CardBackgroundHoverBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button.newProject:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</Styles>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Palette matched to the editor screenshot: near-black background/panels,
|
||||||
|
barely-visible dividers, and a single blue accent. The accent is used
|
||||||
|
ONLY for a selected sidebar tab and the instant a button is pressed -
|
||||||
|
everything else (hover, resting buttons, fields) stays neutral gray.
|
||||||
|
-->
|
||||||
|
<Styles.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
|
||||||
|
<Color x:Key="AppBackgroundColor">#1B1B1D</Color>
|
||||||
|
<Color x:Key="PanelBackgroundColor">#1F1F22</Color>
|
||||||
|
<Color x:Key="SidebarBackgroundColor">#18181A</Color>
|
||||||
|
<Color x:Key="CardBackgroundColor">#222225</Color>
|
||||||
|
<Color x:Key="CardBackgroundHoverColor">#2A2A2E</Color>
|
||||||
|
<Color x:Key="RowSelectedColor">#333338</Color>
|
||||||
|
<Color x:Key="CardBorderColor">#2C2C30</Color>
|
||||||
|
<Color x:Key="DividerColor">#2A2A2E</Color>
|
||||||
|
<Color x:Key="AccentColor">#2F6FED</Color>
|
||||||
|
<Color x:Key="DangerColor">#E5484D</Color>
|
||||||
|
<Color x:Key="DangerPressedColor">#C53A3F</Color>
|
||||||
|
<Color x:Key="TextPrimaryColor">#F2F2F3</Color>
|
||||||
|
<Color x:Key="TextSecondaryColor">#9A9AA0</Color>
|
||||||
|
<Color x:Key="TextMutedColor">#6C6C72</Color>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="AppBackgroundBrush" Color="{StaticResource AppBackgroundColor}" />
|
||||||
|
<SolidColorBrush x:Key="PanelBackgroundBrush" Color="{StaticResource PanelBackgroundColor}" />
|
||||||
|
<SolidColorBrush x:Key="SidebarBackgroundBrush" Color="{StaticResource SidebarBackgroundColor}" />
|
||||||
|
<SolidColorBrush x:Key="CardBackgroundBrush" Color="{StaticResource CardBackgroundColor}" />
|
||||||
|
<SolidColorBrush x:Key="CardBackgroundHoverBrush" Color="{StaticResource CardBackgroundHoverColor}" />
|
||||||
|
<SolidColorBrush x:Key="RowSelectedBrush" Color="{StaticResource RowSelectedColor}" />
|
||||||
|
<SolidColorBrush x:Key="CardBorderBrush" Color="{StaticResource CardBorderColor}" />
|
||||||
|
<SolidColorBrush x:Key="DividerBrush" Color="{StaticResource DividerColor}" />
|
||||||
|
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />
|
||||||
|
<SolidColorBrush x:Key="DangerBrush" Color="{StaticResource DangerColor}" />
|
||||||
|
<SolidColorBrush x:Key="DangerPressedBrush" Color="{StaticResource DangerPressedColor}" />
|
||||||
|
<SolidColorBrush x:Key="TextPrimaryBrush" Color="{StaticResource TextPrimaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="TextSecondaryBrush" Color="{StaticResource TextSecondaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="TextMutedBrush" Color="{StaticResource TextMutedColor}" />
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Styles.Resources>
|
||||||
|
|
||||||
|
<Style Selector="Window">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AppBackgroundBrush}" />
|
||||||
|
<Setter Property="FontFamily" Value="Inter, Segoe UI, Arial" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="ScrollViewer">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</Styles>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using NexvaneHub.Models;
|
||||||
|
|
||||||
|
namespace NexvaneHub.ViewModels;
|
||||||
|
|
||||||
|
public enum HubSection
|
||||||
|
{
|
||||||
|
Projects,
|
||||||
|
Sdk,
|
||||||
|
Packages,
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MainWindowViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public MainWindowViewModel()
|
||||||
|
{
|
||||||
|
_projects = new ObservableCollection<ProjectItemViewModel>
|
||||||
|
{
|
||||||
|
new(new ProjectModel
|
||||||
|
{
|
||||||
|
Name = "Project A",
|
||||||
|
LastModified = new DateTime(2026, 9, 6),
|
||||||
|
Directory = @"C:\Dev\ProjectA",
|
||||||
|
ProjectVersion = "1.2.0",
|
||||||
|
SdkVersion = "1.4.2",
|
||||||
|
DefaultCompiler = "MSVC",
|
||||||
|
DefaultArchitecture = "Auto",
|
||||||
|
}),
|
||||||
|
new(new ProjectModel
|
||||||
|
{
|
||||||
|
Name = "Project B",
|
||||||
|
LastModified = new DateTime(2026, 8, 2),
|
||||||
|
Directory = @"C:\Dev\ProjectB",
|
||||||
|
ProjectVersion = "0.9.4",
|
||||||
|
SdkVersion = "1.3.0",
|
||||||
|
DefaultCompiler = "Clang",
|
||||||
|
DefaultArchitecture = "x64",
|
||||||
|
}),
|
||||||
|
new(new ProjectModel
|
||||||
|
{
|
||||||
|
Name = "Project C",
|
||||||
|
LastModified = new DateTime(2026, 6, 18),
|
||||||
|
Directory = @"C:\Dev\ProjectC",
|
||||||
|
ProjectVersion = "1.0.0",
|
||||||
|
SdkVersion = "1.4.2",
|
||||||
|
DefaultCompiler = "MSVC",
|
||||||
|
DefaultArchitecture = "Auto",
|
||||||
|
}),
|
||||||
|
new(new ProjectModel
|
||||||
|
{
|
||||||
|
Name = "Project D",
|
||||||
|
LastModified = new DateTime(2026, 5, 30),
|
||||||
|
Directory = @"C:\Dev\ProjectD",
|
||||||
|
ProjectVersion = "0.4.1",
|
||||||
|
SdkVersion = "1.2.7",
|
||||||
|
DefaultCompiler = "GCC",
|
||||||
|
DefaultArchitecture = "ARM64",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
_selectedProject = _projects.Count > 0 ? _projects[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private HubSection _currentSection = HubSection.Projects;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<ProjectItemViewModel> _projects;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(OpenProjectCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(DeleteProjectCommand))]
|
||||||
|
private ProjectItemViewModel? _selectedProject;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isProjectSectionExpanded = true;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isSdkSectionExpanded = true;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void NavigateTo(HubSection section)
|
||||||
|
{
|
||||||
|
CurrentSection = section;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(HasSelectedProject))]
|
||||||
|
private void OpenProject()
|
||||||
|
{
|
||||||
|
// TODO: hook this up to actually launching the editor process
|
||||||
|
// against SelectedProject.Directory.
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(HasSelectedProject))]
|
||||||
|
private void DeleteProject()
|
||||||
|
{
|
||||||
|
if (SelectedProject is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = Projects.IndexOf(SelectedProject);
|
||||||
|
Projects.Remove(SelectedProject);
|
||||||
|
|
||||||
|
SelectedProject = Projects.Count == 0
|
||||||
|
? null
|
||||||
|
: Projects[Math.Min(index, Projects.Count - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasSelectedProject() => SelectedProject is not null;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CreateNewProject()
|
||||||
|
{
|
||||||
|
var project = new ProjectItemViewModel(new ProjectModel
|
||||||
|
{
|
||||||
|
Name = $"New Project {Projects.Count + 1}",
|
||||||
|
LastModified = DateTime.Now,
|
||||||
|
Directory = @"C:\Dev\NewProject",
|
||||||
|
});
|
||||||
|
|
||||||
|
Projects.Insert(0, project);
|
||||||
|
SelectedProject = project;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using NexvaneHub.Models;
|
||||||
|
|
||||||
|
namespace NexvaneHub.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Editable wrapper around a ProjectModel. Every property here is a live,
|
||||||
|
/// bindable field that writes straight back onto the wrapped Model when
|
||||||
|
/// changed - except Directory, which stays read-only since it's tied to the
|
||||||
|
/// project's location on disk rather than something you'd retype.
|
||||||
|
/// </summary>
|
||||||
|
public partial class ProjectItemViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public ProjectItemViewModel(ProjectModel model)
|
||||||
|
{
|
||||||
|
Model = model;
|
||||||
|
|
||||||
|
_name = model.Name;
|
||||||
|
_lastModified = model.LastModified;
|
||||||
|
_directory = model.Directory;
|
||||||
|
_thumbnailPath = model.ThumbnailPath;
|
||||||
|
_projectVersion = model.ProjectVersion;
|
||||||
|
_sdkVersion = model.SdkVersion;
|
||||||
|
_defaultCompiler = model.DefaultCompiler;
|
||||||
|
_defaultArchitecture = model.DefaultArchitecture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProjectModel Model { get; }
|
||||||
|
|
||||||
|
public string LastModifiedDisplay => LastModified.ToString("dd-MM-yyyy");
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(LastModifiedDisplay))]
|
||||||
|
private DateTime _lastModified;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _name;
|
||||||
|
|
||||||
|
/// <summary>Read-only in the UI - displayed via a plain field, not a TextBox.</summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _directory;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _thumbnailPath;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _projectVersion;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _sdkVersion;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _defaultCompiler;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _defaultArchitecture;
|
||||||
|
|
||||||
|
partial void OnNameChanged(string value) => Model.Name = value;
|
||||||
|
partial void OnThumbnailPathChanged(string value) => Model.ThumbnailPath = value;
|
||||||
|
partial void OnProjectVersionChanged(string value) => Model.ProjectVersion = value;
|
||||||
|
partial void OnSdkVersionChanged(string value) => Model.SdkVersion = value;
|
||||||
|
partial void OnDefaultCompilerChanged(string value) => Model.DefaultCompiler = value;
|
||||||
|
partial void OnDefaultArchitectureChanged(string value) => Model.DefaultArchitecture = value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace NexvaneHub.ViewModels;
|
||||||
|
|
||||||
|
public abstract class ViewModelBase : ObservableObject
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:NexvaneHub.ViewModels"
|
||||||
|
xmlns:conv="using:NexvaneHub.Converters"
|
||||||
|
xmlns:avc="using:Avalonia.Data.Converters"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:Class="NexvaneHub.Views.MainWindow"
|
||||||
|
x:DataType="vm:MainWindowViewModel"
|
||||||
|
Title="Nexvane Hub"
|
||||||
|
Width="1180" Height="740"
|
||||||
|
MinWidth="920" MinHeight="600"
|
||||||
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaChromeHints="NoChrome"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="62">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:MainWindowViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="62,*">
|
||||||
|
|
||||||
|
<!-- ===== Title bar (fully custom - see MainWindow.axaml.cs for the
|
||||||
|
drag/minimize/maximize/close handlers this wires up to) ===== -->
|
||||||
|
<Border Grid.Row="0" Classes="topBar" PointerPressed="TitleBar_PointerPressed">
|
||||||
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
|
|
||||||
|
<!-- Logo spans the full height of both rows below, flush in the
|
||||||
|
top-left corner (like the editor's), not confined to the
|
||||||
|
title row alone. Vector geometry lifted straight from the
|
||||||
|
provided nexvane-logo.svg - see Assets/nexvane-logo.svg for
|
||||||
|
the source. Coordinates have the SVG's scale/translate baked
|
||||||
|
in directly (computed offline) rather than applied via
|
||||||
|
RenderTransform, so there's no Viewbox+transform reconciliation
|
||||||
|
for anything to get wrong - what's below is the tight bounding
|
||||||
|
box of the mark itself, plus a small safety margin. Sized to
|
||||||
|
fill most of its column's height (Border.topBar's padding trims
|
||||||
|
a few px off each end) rather than floating small in the
|
||||||
|
middle, so the text rows don't visually overshoot the mark. -->
|
||||||
|
<Border Grid.Column="0" Classes="logoBadge">
|
||||||
|
<Viewbox Width="36" Height="40" Stretch="Uniform" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||||
|
<Canvas Width="835.96" Height="934.96">
|
||||||
|
<Path Fill="White" Data="M 17.980,100.680 L 17.980,183.480 L 100.480,100.980 C 145.880,55.580 182.980,18.380 182.980,18.280 C 182.980,18.080 145.880,17.980 100.480,17.980 L 17.980,17.980 L 17.980,100.680" />
|
||||||
|
<Path Fill="White" Data="M 719.680,114.780 L 622.980,211.480 L 622.980,343.780 C 622.980,476.680 623.380,503.480 626.980,584.380 C 628.080,608.580 628.780,628.580 628.580,628.780 C 627.880,629.380 648.480,664.780 369.180,182.980 C 334.280,122.780 304.180,70.880 302.180,67.580 L 298.680,61.780 L 158.380,202.080 L 17.980,342.480 L 17.980,624.480 L 17.980,906.480 L 114.980,809.480 L 211.980,712.480 L 211.980,585.980 C 211.980,516.380 211.480,447.580 210.980,432.980 C 209.780,401.480 207.380,351.080 205.580,320.480 C 204.780,308.380 204.380,298.380 204.480,298.180 C 204.880,297.680 199.980,289.280 365.980,577.980 C 452.480,728.380 524.980,854.580 527.180,858.380 L 531.180,865.280 L 674.580,721.880 L 817.980,578.480 L 817.980,298.280 C 817.980,144.080 817.680,17.980 817.180,17.980 C 816.780,17.980 772.980,61.480 719.680,114.780" />
|
||||||
|
<Path Fill="White" Data="M 727.480,826.980 L 637.480,916.980 L 727.780,916.980 L 817.980,916.980 L 817.980,826.980 C 817.980,777.480 817.880,736.980 817.680,736.980 C 817.580,736.980 776.980,777.480 727.480,826.980" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1" Margin="20,0,20,0">
|
||||||
|
<Grid Height="34" ColumnDefinitions="*,Auto">
|
||||||
|
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<TextBlock Classes="appTitle" Text="Nexvane" />
|
||||||
|
<TextBlock Classes="appTitleMuted" Text="Hub" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Spacing="0">
|
||||||
|
<Button Classes="iconButton" Content="⚙" ToolTip.Tip="Settings" Margin="0,0,8,0" />
|
||||||
|
<Button Classes="captionButton" Content="—" Click="MinimizeButton_Click" ToolTip.Tip="Minimize" />
|
||||||
|
<Button Classes="captionButton" Content="□" Click="MaximizeButton_Click" ToolTip.Tip="Maximize" />
|
||||||
|
<Button Classes="captionButton captionButtonClose" Content="✕" Click="CloseButton_Click" ToolTip.Tip="Close" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Menu row sits below the title, not above it. Fixed height
|
||||||
|
(34 + 22 = 56, plus Border.topBar's 3px top/bottom padding =
|
||||||
|
62 total) rather than auto-sized from padding/margin, so the
|
||||||
|
bar's total height is a known number instead of something
|
||||||
|
several nested layers of content have to happen to agree on.
|
||||||
|
Both rows hug their content tightly rather than centering
|
||||||
|
inside oversized fixed heights, and the shared 3px padding on
|
||||||
|
Border.topBar keeps everything off the bar's very top/bottom
|
||||||
|
edges. Each item opens a dropdown via Button.Flyout - Avalonia
|
||||||
|
handles positioning, click-away-to-close, and Escape for us;
|
||||||
|
the content inside is ours. Margin.Left is negative to cancel
|
||||||
|
out Button.menuBarItem's own 6px left padding - otherwise
|
||||||
|
"File"'s text sits right of where "Nexvane" starts, since the
|
||||||
|
title TextBlock has no padding of its own to match against. -->
|
||||||
|
<StackPanel Orientation="Horizontal" Height="22" VerticalAlignment="Top" Spacing="12" Margin="-6,0,0,0">
|
||||||
|
|
||||||
|
<Button Classes="menuBarItem" Content="File">
|
||||||
|
<Button.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel MinWidth="190">
|
||||||
|
<Button Classes="dropdownItem" Content="New Project" />
|
||||||
|
<Button Classes="dropdownItem" Content="Open Project" />
|
||||||
|
<Border Classes="dropdownSeparator" />
|
||||||
|
<Button Classes="dropdownItem" Content="Save" />
|
||||||
|
<Button Classes="dropdownItem" Content="Save As..." />
|
||||||
|
<Border Classes="dropdownSeparator" />
|
||||||
|
<Button Classes="dropdownItem" Content="Exit" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="menuBarItem" Content="Edit">
|
||||||
|
<Button.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel MinWidth="190">
|
||||||
|
<Button Classes="dropdownItem" Content="Undo" />
|
||||||
|
<Button Classes="dropdownItem" Content="Redo" />
|
||||||
|
<Border Classes="dropdownSeparator" />
|
||||||
|
<Button Classes="dropdownItem" Content="Preferences" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="menuBarItem" Content="View">
|
||||||
|
<Button.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel MinWidth="190">
|
||||||
|
<Button Classes="dropdownItem" Content="Toggle Sidebar" />
|
||||||
|
<Button Classes="dropdownItem" Content="Toggle Detail Panel" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="menuBarItem" Content="Tools">
|
||||||
|
<Button.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel MinWidth="190">
|
||||||
|
<Button Classes="dropdownItem" Content="SDK Manager" />
|
||||||
|
<Button Classes="dropdownItem" Content="Package Manager" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="menuBarItem" Content="Help">
|
||||||
|
<Button.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel MinWidth="190">
|
||||||
|
<Button Classes="dropdownItem" Content="Documentation" />
|
||||||
|
<Border Classes="dropdownSeparator" />
|
||||||
|
<Button Classes="dropdownItem" Content="About Nexvane Hub" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ===== Body ===== -->
|
||||||
|
<Grid Grid.Row="1" ColumnDefinitions="210,*,360">
|
||||||
|
|
||||||
|
<!-- Sidebar navigation -->
|
||||||
|
<Border Grid.Column="0" Classes="sidebar">
|
||||||
|
<Grid RowDefinitions="*,Auto">
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
|
<Button Classes="navItem"
|
||||||
|
Classes.selected="{Binding CurrentSection, Converter={x:Static conv:EnumEqualsConverter.Instance}, ConverterParameter=Projects}"
|
||||||
|
Command="{Binding NavigateToCommand}"
|
||||||
|
CommandParameter="{x:Static vm:HubSection.Projects}"
|
||||||
|
Content="Projects" />
|
||||||
|
<Button Classes="navItem"
|
||||||
|
Classes.selected="{Binding CurrentSection, Converter={x:Static conv:EnumEqualsConverter.Instance}, ConverterParameter=Sdk}"
|
||||||
|
Command="{Binding NavigateToCommand}"
|
||||||
|
CommandParameter="{x:Static vm:HubSection.Sdk}"
|
||||||
|
Content="SDK" />
|
||||||
|
<Button Classes="navItem"
|
||||||
|
Classes.selected="{Binding CurrentSection, Converter={x:Static conv:EnumEqualsConverter.Instance}, ConverterParameter=Packages}"
|
||||||
|
Command="{Binding NavigateToCommand}"
|
||||||
|
CommandParameter="{x:Static vm:HubSection.Packages}"
|
||||||
|
Content="Packages" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Row="1" Classes="accountItem" Content="Account" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Middle column: project list (with New folded in as its footer) / other sections -->
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
|
||||||
|
<!-- Everything for the Projects section - list + New - lives inside
|
||||||
|
ONE rounded, clipped container, so New reads as part of the
|
||||||
|
card rather than a separate floating button below it. -->
|
||||||
|
<Border Classes="listContainer"
|
||||||
|
Margin="16"
|
||||||
|
IsVisible="{Binding CurrentSection, Converter={x:Static conv:EnumEqualsConverter.Instance}, ConverterParameter=Projects}">
|
||||||
|
<Grid RowDefinitions="*,Auto">
|
||||||
|
<ScrollViewer Grid.Row="0" Padding="0,0,14,0">
|
||||||
|
<ListBox Classes="projectList"
|
||||||
|
ItemsSource="{Binding Projects}"
|
||||||
|
SelectedItem="{Binding SelectedProject}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ProjectItemViewModel">
|
||||||
|
<Border Classes="listRow">
|
||||||
|
<Grid ColumnDefinitions="56,*" Margin="14,10">
|
||||||
|
<Border Grid.Column="0" Classes="thumb" />
|
||||||
|
<StackPanel Grid.Column="1" Margin="12,0,0,0" VerticalAlignment="Center">
|
||||||
|
<TextBlock Classes="cardTitle" Text="{Binding Name}" />
|
||||||
|
<TextBlock Classes="cardSubtitle" Text="{Binding LastModifiedDisplay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Border Grid.Row="1" BorderBrush="{StaticResource DividerBrush}" BorderThickness="0,1,0,0">
|
||||||
|
<Button Classes="newProject"
|
||||||
|
Content="+ New"
|
||||||
|
Command="{Binding CreateNewProjectCommand}"
|
||||||
|
CornerRadius="0"
|
||||||
|
BorderThickness="0"
|
||||||
|
Margin="0" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Padding="20"
|
||||||
|
IsVisible="{Binding CurrentSection, Converter={x:Static conv:EnumEqualsConverter.Instance}, ConverterParameter=Sdk}">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="SDK management goes here." />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Padding="20"
|
||||||
|
IsVisible="{Binding CurrentSection, Converter={x:Static conv:EnumEqualsConverter.Instance}, ConverterParameter=Packages}">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="Installed packages go here." />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Right column: selected project detail - only part of the Projects
|
||||||
|
tab, not shown while viewing SDK/Packages. -->
|
||||||
|
<Border Grid.Column="2" Classes="detailPanel"
|
||||||
|
IsVisible="{Binding CurrentSection, Converter={x:Static conv:EnumEqualsConverter.Instance}, ConverterParameter=Projects}">
|
||||||
|
<Grid IsVisible="{Binding SelectedProject, Converter={x:Static avc:ObjectConverters.IsNotNull}}"
|
||||||
|
RowDefinitions="Auto,*,Auto"
|
||||||
|
Margin="18">
|
||||||
|
|
||||||
|
<TextBox Grid.Row="0"
|
||||||
|
Classes="input titleInput"
|
||||||
|
Text="{Binding SelectedProject.Name, Mode=TwoWay}" />
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" Margin="0,14,0,0" Padding="0,0,14,0">
|
||||||
|
<StackPanel>
|
||||||
|
<Border Classes="thumbLarge" />
|
||||||
|
|
||||||
|
<ToggleButton Classes="sectionHeader"
|
||||||
|
Content="▾ Project"
|
||||||
|
IsChecked="{Binding IsProjectSectionExpanded}"
|
||||||
|
Margin="0,16,0,0" />
|
||||||
|
<StackPanel IsVisible="{Binding IsProjectSectionExpanded}" Margin="0,10,0,0" Spacing="10">
|
||||||
|
<Grid ColumnDefinitions="100,*">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="Directory" />
|
||||||
|
<Border Grid.Column="1" Classes="fieldBox">
|
||||||
|
<TextBlock Classes="fieldValueMuted"
|
||||||
|
Text="{Binding SelectedProject.Directory}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="100,*">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="Thumbnail" />
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Classes="input"
|
||||||
|
Text="{Binding SelectedProject.ThumbnailPath, Mode=TwoWay}"
|
||||||
|
Watermark="No thumbnail set" />
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="100,*">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="Version" />
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Classes="input"
|
||||||
|
Text="{Binding SelectedProject.ProjectVersion, Mode=TwoWay}" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ToggleButton Classes="sectionHeader"
|
||||||
|
Content="▾ SDK"
|
||||||
|
IsChecked="{Binding IsSdkSectionExpanded}"
|
||||||
|
Margin="0,14,0,0" />
|
||||||
|
<StackPanel IsVisible="{Binding IsSdkSectionExpanded}" Margin="0,10,0,0" Spacing="10">
|
||||||
|
<Grid ColumnDefinitions="100,*">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="Version" />
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Classes="input"
|
||||||
|
Text="{Binding SelectedProject.SdkVersion, Mode=TwoWay}" />
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="100,*">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="Def. Compiler" />
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Classes="input"
|
||||||
|
Text="{Binding SelectedProject.DefaultCompiler, Mode=TwoWay}" />
|
||||||
|
</Grid>
|
||||||
|
<Grid ColumnDefinitions="100,*">
|
||||||
|
<TextBlock Classes="fieldLabel" Text="Def. Arch" />
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
Classes="input"
|
||||||
|
Text="{Binding SelectedProject.DefaultArchitecture, Mode=TwoWay}" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- Open/Delete as one rounded, clipped block with a hairline
|
||||||
|
divider between them - a visible seam so the pair doesn't
|
||||||
|
read as one smeared-together shape, but still no gap/margin
|
||||||
|
between the two buttons themselves. -->
|
||||||
|
<Border Grid.Row="2"
|
||||||
|
Margin="0,16,0,0"
|
||||||
|
CornerRadius="8"
|
||||||
|
ClipToBounds="True"
|
||||||
|
BorderBrush="{StaticResource CardBorderBrush}"
|
||||||
|
BorderThickness="1">
|
||||||
|
<StackPanel Spacing="0">
|
||||||
|
<Button Classes="primary"
|
||||||
|
Content="Open"
|
||||||
|
Command="{Binding OpenProjectCommand}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
CornerRadius="0"
|
||||||
|
BorderThickness="0" />
|
||||||
|
<Border Height="1" Background="{StaticResource CardBorderBrush}" />
|
||||||
|
<Button Classes="danger"
|
||||||
|
Content="Delete"
|
||||||
|
Command="{Binding DeleteProjectCommand}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
CornerRadius="0"
|
||||||
|
BorderThickness="0" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace NexvaneHub.Views;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The title bar has no native chrome anymore (ExtendClientAreaChromeHints
|
||||||
|
// = NoChrome), so dragging the window has to be done by hand. Buttons in
|
||||||
|
// the title bar (settings gear, minimize/maximize/close) consume their
|
||||||
|
// own PointerPressed before it reaches here, so this only fires for
|
||||||
|
// clicks on the blank part of the bar.
|
||||||
|
private void TitleBar_PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
|
{
|
||||||
|
BeginMoveDrag(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MinimizeButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
WindowState = WindowState.Minimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MaximizeButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
WindowState = WindowState == WindowState.Maximized
|
||||||
|
? WindowState.Normal
|
||||||
|
: WindowState.Maximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user