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