OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / AvaloniaShell / MainWindow.axaml

601 lines · axaml ref:
<Window xmlns="https://github.com/avaloniaui"
        DragDrop.AllowDrop="True"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:IFSRenderer.AvaloniaShell"
        xmlns:c="clr-namespace:IFSRenderer.AvaloniaShell.Controls"
        x:Class="IFSRenderer.AvaloniaShell.MainWindow"
        Title="IFSRenderer" Width="1500" Height="900" WindowState="Maximized"
        FontSize="13"
        Background="#323232">

  <Window.Styles>
    <!--
      Stands in for an AvalonDock pane title bar, which the canonical gets from the docking
      layout and this port has to draw itself. The invented #8ab4f8 caption colour is gone; the
      band is lifted just clear of the #1c1e21 pane ground, since painting it the SAME colour as
      the pane made every section header invisible.

      It deliberately does NOT bleed outwards with a negative margin. It used to, to reach the
      pane edge, which meant the margin had to equal its container's inset exactly — and every
      container that got it wrong clipped the title against its edge. It now spans the content
      width instead, so it is correct in any container.
    -->
    <Style Selector="TextBlock.section">
      <Setter Property="FontWeight" Value="SemiBold" />
      <Setter Property="Foreground" Value="#e8e8e8" />
      <Setter Property="Background" Value="#2c3035" />
      <Setter Property="Padding" Value="6,3" />
      <Setter Property="Margin" Value="0,14,0,6" />
    </Style>
    <!--
      md:Card. Material.Avalonia 3.14 ships no Card control (only a Card.axaml style file),
      so this reproduces the measured appearance directly: #454545 on the #323232 paper,
      rounded, with a two-layer elevation shadow.
    -->
    <Style Selector="Border.card">
      <Setter Property="Background" Value="#454545" />
      <Setter Property="CornerRadius" Value="4" />
      <Setter Property="BoxShadow" Value="0 1 3 0 #55000000, 0 4 10 0 #33000000" />
    </Style>

    <Style Selector="TextBlock.label">
      <Setter Property="Foreground" Value="#cfcfcf" />
      <Setter Property="FontSize" Value="13" />
    </Style>
    <Style Selector="TextBlock.value">
      <Setter Property="Foreground" Value="#e0e0e6" />
      <Setter Property="FontSize" Value="12" />
      <Setter Property="HorizontalAlignment" Value="Right" />
    </Style>
    <!--
      Scrollbars.

      Avalonia's ScrollViewer template lets the content presenter span the scrollbar's column,
      so the bar always FLOATS over the content. AllowAutoHide="False" only stops it collapsing
      to a hairline - it does not reserve any space, which an earlier comment here claimed it
      did. The bar is therefore narrowed to a thin strip and every scrolled panel leaves a
      right-hand gutter at least that wide, so nothing is drawn underneath it.
    -->
    <Style Selector="ScrollBar:vertical">
      <Setter Property="Width" Value="10" />
      <Setter Property="MinWidth" Value="10" />
      <Setter Property="MaxWidth" Value="10" />
    </Style>
    <Style Selector="ScrollBar:vertical /template/ Thumb">
      <Setter Property="MinWidth" Value="6" />
      <Setter Property="Width" Value="6" />
    </Style>

  </Window.Styles>

  <DockPanel>

    <!--
      MainWindow.xaml's ToolBarTray: the File menu and its quick 💾 ↩️ ↪️ items, the window
      commands, the editable fractal title, the Export menu, and Tutorials/Help pinned right.
      The View menu has no canonical counterpart - AvalonDock owned pane visibility there.
    -->
    <Grid DockPanel.Dock="Top" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,*,Auto"
          Background="#212121">

      <Menu Grid.Column="0">
        <MenuItem Header="📑 _File">
          <MenuItem Name="MenuNew" Header="📑 _New" InputGesture="Ctrl+N" />
          <MenuItem Name="MenuTemplates" Header="🗂️ _Templates" />
          <MenuItem Name="MenuRecent" Header="📜 Recents" />
          <MenuItem Name="MenuOpen" Header="📂 _Load…" InputGesture="Ctrl+O" />
          <MenuItem Name="MenuSave" Header="💾 _Save" InputGesture="Ctrl+S" />
          <MenuItem Name="MenuSaveAs" Header="💾 _Save as…" InputGesture="Ctrl+Shift+S" />
          <Separator />
          <MenuItem Name="MenuCopyParams" Header="📝 Copy params" InputGesture="Ctrl+C" />
          <MenuItem Name="MenuPasteParams" Header="📋 Paste params" InputGesture="Ctrl+V" />
          <Separator />
          <MenuItem Name="MenuSettings" Header="⚙️ Settings…" />
          <Separator />
          <MenuItem Name="MenuExit" Header="🏃 Exit" />
        </MenuItem>
        <MenuItem Name="MenuSaveQuick" Header="💾" ToolTip.Tip="Save (Ctrl+S)" />
        <MenuItem Name="MenuUndo" Header="↩️" InputGesture="Ctrl+Z" ToolTip.Tip="Undo (Ctrl+Z)" />
        <MenuItem Name="MenuRedo" Header="↪️" InputGesture="Ctrl+Y" ToolTip.Tip="Redo (Ctrl+Y)" />
      </Menu>

      <Separator Grid.Column="1" Margin="6,4" Width="1" Background="#3a3a3a" />

      <Menu Grid.Column="2">
        <MenuItem Name="MenuGenerator" Header="🔮     Generator" ToolTip.Tip="Open the generator window" />
        <MenuItem Name="MenuRandom" Header="🎲     _Random" InputGesture="Ctrl+R"
                  ToolTip.Tip="Generate random params (Ctrl+R)" />
      </Menu>

      <Separator Grid.Column="3" Margin="6,4" Width="1" Background="#3a3a3a" />

      <!--
        TitleBox.xaml: a 200x30 centred, heavy-weight box holding the fractal's title, with a
        toggle beside it opening the authors popup (TitleBox.xaml:22-45). Author was WRITE-only
        here - it is stamped into exported PNGs but never shown - so opening someone else's
        .ifsjson gave no way to see who made it or visit them, discarding the attribution the
        format carries.
      -->
      <StackPanel Grid.Column="4" Orientation="Horizontal" VerticalAlignment="Center" Margin="4,0">
        <TextBox Name="TitleBox" Width="200" Height="30"
                 TextAlignment="Center" FontWeight="Black" VerticalAlignment="Center"
                 Watermark="Enter a title" ToolTip.Tip="Title" />
        <Button Name="BtnAuthors" Classes="Flat" Content="👤" Padding="6,0" MinWidth="0"
                VerticalAlignment="Center" ToolTip.Tip="Authors">
          <Button.Flyout>
            <Flyout Placement="Bottom">
              <StackPanel Width="220" Name="AuthorsPanel">
                <TextBlock Classes="label" Text="Authors" FontWeight="SemiBold" Margin="0,0,0,4" />
                <StackPanel Name="AuthorsList" />
              </StackPanel>
            </Flyout>
          </Button.Flyout>
        </Button>
      </StackPanel>

      <Menu Grid.Column="5" HorizontalAlignment="Left">
        <MenuItem Header="🖼 E_xport">
          <MenuItem Name="MenuExportClipboard" Header="📋 Export to _clipboard" InputGesture="Ctrl+Shift+C" />
          <MenuItem Name="MenuExportExr" Header="📊 Export to _EXR…" InputGesture="Ctrl+Shift+E" />
          <MenuItem Name="MenuExportPng" Header="🖼 Export to _PNG…" InputGesture="Ctrl+E" />
          <Separator />
          <MenuItem Name="MenuExportFrames" Header="🎥 Export _animation frames…" InputGesture="Ctrl+A" />
          <MenuItem Name="MenuExportVideo" Header="🎬 Export _video…" InputGesture="Ctrl+Shift+A" />
        </MenuItem>
        <MenuItem Header="_View">
          <MenuItem Name="MenuToggleRender" Header="Pause _rendering" InputGesture="Space" />
          <MenuItem Name="MenuToggleHints" Header="Show control _hints" />
          <Separator />
          <MenuItem Name="MenuPanelIterators" Header="_Tone mapping panel" />
          <MenuItem Name="MenuPanelProperties" Header="_Properties panel" />
          <MenuItem Name="MenuPanelGraph" Header="Node _graph panel" />
          <Separator />
          <MenuItem Name="MenuResetCamera" Header="Reset _camera" />
          <MenuItem Name="MenuAutoLayout" Header="Auto-_layout nodes" />
        </MenuItem>
      </Menu>

      <Menu Grid.Column="6" HorizontalAlignment="Right">
        <MenuItem Name="MenuTutorials" Header="🎓     Tutorials…" InputGesture="F1" />
        <MenuItem Header="Help">
          <MenuItem Name="MenuWiki" Header="📚 Wiki…" InputGesture="F1" />
          <MenuItem Name="MenuForum" Header="🏛️ Forum…" />
          <MenuItem Header="🙋 Send Feedback">
            <MenuItem Name="MenuReportBug" Header="🐞 Report a bug…" />
            <MenuItem Name="MenuIssues" Header="🙋 File an issue…" />
          </MenuItem>
          <Separator />
          <MenuItem Name="MenuAbout" Header="👷 About…" />
          <MenuItem Name="MenuCheckUpdates" Header="💿 Check for updates…" />
        </MenuItem>
      </Menu>

    </Grid>

    <!--
      MainWindow.xaml:290-351. The canonical status bar carries a 100px iteration ProgressBar
      with a "Progress: n / m" caption and a play/pause ToggleButton; ours had status text and
      the GPU name only, so IterationLevel and IterationProgressPercent had no surface at all
      and a long render gave no sense of completion.

      The gamepad indicator is deliberately absent - this port has no gamepad support.
    -->
    <Border DockPanel.Dock="Bottom" Classes="card" Padding="3" Margin="8">
      <Grid ColumnDefinitions="*,Auto,Auto,Auto">
        <TextBlock Name="StatusText" Grid.Column="0" Foreground="#c8c8d0" FontSize="12"
                   Text="Starting…" VerticalAlignment="Center" />

        <Grid Grid.Column="1" Width="110" Margin="8,0" VerticalAlignment="Center">
          <ProgressBar Name="IterationProgress" Minimum="0" Maximum="100"
                       VerticalAlignment="Bottom" Height="4" />
          <TextBlock Name="ProgressText" Foreground="White" FontSize="11"
                     TextAlignment="Center" VerticalAlignment="Top"
                     ToolTip.Tip="Iteration progress" Text="Progress: 0 / 0" />
        </Grid>

        <Button Name="BtnToggleRender" Grid.Column="2" Classes="Flat"
                Width="22" Height="22" Padding="0" MinWidth="0" FontFamily="monospace"
                Content="||" VerticalAlignment="Center" Margin="0,0,8,0"
                ToolTip.Tip="Resume/Pause rendering (Pause key)" />

        <TextBlock Name="GpuText" Grid.Column="3" Foreground="#d3d3d3" FontSize="12"
                   VerticalAlignment="Center" />
      </Grid>
    </Border>

    <!--
      The three panes sit in a Grid with GridSplitters, so they can be resized as the
      AvalonDock panes they stand in for are. TogglePanel collapses a pane by zeroing its
      column, since hiding the Border alone would leave the column occupying space.
    -->
    <Grid Name="PaneGrid" ColumnDefinitions="200,4,*,4,250">

    <!--
      Sides follow MainWindow.xaml:148-257 - Tonemapping / Environment / Camera on the LEFT at
      200px, Rendering Settings / Post Fx / Performance on the RIGHT at 250px. Selected Iterator
      and Selected Connection are not main-window panes at all: they belong to the Editor, so
      they live with the node graph.
    -->
    <Border Name="LeftPanel" Grid.Column="0" Background="#1c1e21" MinWidth="140">
      <ScrollViewer VerticalScrollBarVisibility="Visible" AllowAutoHide="False"
                    HorizontalScrollBarVisibility="Disabled">
        <StackPanel Margin="12,4,26,12">
          <TextBlock Classes="section" Text="TONE MAPPING" />
          <!-- Labels, defaults, increments and (absent) maxima come from
          WpfDisplay/ViewModels/ToneMappingViewModel.cs — see WireToneMapping(). -->
          <c:ValueSlider Name="BrightnessSlider" />
          <c:ValueSlider Name="GammaSlider" />
          <c:ValueSlider Name="GammaThresholdSlider" />
          <c:ValueSlider Name="VibrancySlider" />
          <TextBlock Classes="section" Text="ENVIRONMENT" />
          <!--
            MainWindow.xaml:171 is an xctk:ColorPicker: one swatch-sized control that opens a
            picker. Three stock 0-255 Sliders had no canonical counterpart at all, and worse,
            the read-outs beside them were one-way StringFormat - so this was the only place in
            the app where a value could not be reached from the keyboard. An exact colour (a
            brand hex, a value copied from another params file) simply could not be entered.

            Hex entry plus the app's own ValueSliders keeps the canonical's shape and stays in
            the Material theme, which Avalonia's own ColorPicker would not - it ships no Material
            styling, so it would render in Fluent chrome.
          -->
          <Grid ColumnDefinitions="*,Auto" Margin="0,0,0,4">
          <TextBlock Classes="label" Text="Background" VerticalAlignment="Center" />
          <Button Grid.Column="1" Name="BackgroundButton" Classes="Flat" Padding="2" MinWidth="0"
          HorizontalAlignment="Right" ToolTip.Tip="Background colour">
          <Border Name="BackgroundSwatch" Width="34" Height="16"
          BorderBrush="#55ffffff" BorderThickness="1" CornerRadius="2" Background="Black" />
          <Button.Flyout>
          <Flyout Placement="Bottom">
          <StackPanel Width="220">
          <TextBlock Classes="label" Text="Hex" Margin="0,0,0,2" />
          <TextBox Name="BgHex" Watermark="#000000" Margin="0,0,0,4" />
          <c:ValueSlider Name="BgR" />
          <c:ValueSlider Name="BgG" />
          <c:ValueSlider Name="BgB" />
          </StackPanel>
          </Flyout>
          </Button.Flyout>
          </Button>
          </Grid>
          <c:ValueSlider Name="FogSlider" />
          <TextBlock Classes="section" Text="CAMERA" />
          <!-- CameraSettingsViewModel.cs. Note Focus Distance has no bounds at all, and
          Aperture steps by 0.0001 — the previous 0–0.5 slider could not resolve it. -->
          <c:ValueSlider Name="FovSlider" />
          <c:ValueSlider Name="ApertureSlider" />
          <c:ValueSlider Name="FocusSlider" />
          <c:ValueSlider Name="DofSlider" />
          <TextBlock Classes="label" Text="Projection" Margin="0,6,0,2" />
          <ComboBox Name="ProjectionCombo" HorizontalAlignment="Stretch" />

          <!-- The canonical Navigation pane stacks these as unlabelled ValueSliders under a
          group heading, with the keyframe diamond inline on the right (IsLabelShown=false).
          Animate Camera keys all seven at once. -->
          <TextBlock Classes="label" Text="Position" Margin="0,8,0,2" />
          <c:ValueSlider Name="PosX" />
          <c:ValueSlider Name="PosY" />
          <c:ValueSlider Name="PosZ" />
          <TextBlock Classes="label" Text="Orientation" Margin="0,6,0,2" />
          <c:ValueSlider Name="OriX" />
          <c:ValueSlider Name="OriY" />
          <c:ValueSlider Name="OriZ" />
          <c:ValueSlider Name="OriW" />
          <Button Name="BtnAnimateCamera" Content="🎬 Animate Camera"
          HorizontalAlignment="Stretch" Margin="0,6,0,0"
          ToolTip.Tip="Insert keyframes on all seven camera channels" />
          <TextBlock Classes="label" Text="Projection tilt (dome)" Margin="0,8,0,2" />
          <c:ValueSlider Name="TiltX" />
          <c:ValueSlider Name="TiltY" />
          <c:ValueSlider Name="TiltZ" />

          <TextBlock Classes="label" Text="Navigate" Margin="0,8,0,2" />
          <Grid ColumnDefinitions="*,*,*" RowDefinitions="Auto,Auto,Auto" >
          <Button Name="NavUp" Grid.Row="0" Grid.Column="1" Content="▲" HorizontalAlignment="Stretch" Margin="1" />
          <Button Name="NavLeft" Grid.Row="1" Grid.Column="0" Content="◀" HorizontalAlignment="Stretch" Margin="1" />
          <Button Name="NavHome" Grid.Row="1" Grid.Column="1" Content="⌂" HorizontalAlignment="Stretch" Margin="1" />
          <Button Name="NavRight" Grid.Row="1" Grid.Column="2" Content="▶" HorizontalAlignment="Stretch" Margin="1" />
          <Button Name="NavDown" Grid.Row="2" Grid.Column="1" Content="▼" HorizontalAlignment="Stretch" Margin="1" />
          <Button Name="NavIn" Grid.Row="2" Grid.Column="0" Content="+" HorizontalAlignment="Stretch" Margin="1"
          ToolTip.Tip="Move closer" />
          <Button Name="NavOut" Grid.Row="2" Grid.Column="2" Content="-" HorizontalAlignment="Stretch" Margin="1"
          ToolTip.Tip="Move away" />
          </Grid>

          <CheckBox Name="InvertY" Content="Invert Y axis" Margin="0,8,0,0" />
          <c:ValueSlider Name="Sensitivity" />
        </StackPanel>
      </ScrollViewer>
    </Border>

    <GridSplitter Name="LeftSplitter" Grid.Column="1" Background="#2a2c30" ResizeDirection="Columns" />

    <!-- Properties -->
    <!--
      Padding lives on the scrolled content, not the pane, so the ScrollViewer spans the full
      pane width and its scrollbar occupies its own strip at the right edge instead of floating
      over the controls.
    -->
    <Border Name="PropertiesPanel" Grid.Column="4" Background="#1c1e21" MinWidth="160">
      <!--
        AllowAutoHide=false keeps the scrollbar in the layout instead of floating it over the
        parameters - the overlay bar was covering the right edge of every control.
      -->
      <ScrollViewer VerticalScrollBarVisibility="Visible" AllowAutoHide="False"
                    HorizontalScrollBarVisibility="Disabled">
        <StackPanel Margin="12,4,26,12">

          <TextBlock Classes="section" Text="QUALITY" />
          <!-- QualitySettingsViewModel.cs. The canonical UI exposes SYNTROPY (10..1000000,
          step 10) rather than raw entropy; syntropy is 1/entropy. -->
          <c:ValueSlider Name="QualitySlider" />
          <c:ValueSlider Name="SyntropySlider" />
          <c:ValueSlider Name="WarmupSlider" />
          <c:ValueSlider Name="FilterRadiusSlider" />
          <CheckBox Name="FinalRenderMode" Content="Final render mode (1:1)" Margin="0,6,0,0" />
          <TextBlock Classes="section" Text="DENSITY ESTIMATION" />
          <CheckBox Name="EnableDe" Content="Enabled" />
          <c:ValueSlider Name="DeRadiusSlider" />
          <c:ValueSlider Name="DePowerSlider" />
          <c:ValueSlider Name="DeThresholdSlider" />
          <TextBlock Classes="section" Text="POST-PROCESSING" />
          <!--
          PostFxPanel.xaml: an add-layer combo overlaid with "+ Add layer", reload and
          open-directory tool buttons, then one Expander per effect layer. Each layer owns
          its enable checkbox and its parameters; there is no separate selection.
          -->
          <Grid ColumnDefinitions="*,Auto,Auto" Margin="0,4">
          <Grid Grid.Column="0">
          <ComboBox Name="PostFxAddCombo" HorizontalAlignment="Stretch" MaxDropDownHeight="300" />
          <TextBlock Text="+ Add layer" Margin="8" IsHitTestVisible="False"
          VerticalAlignment="Center" />
          </Grid>
          <Button Name="BtnReloadEffects" Grid.Column="1" Content="🔄" Classes="Flat" Background="Transparent"
          BorderThickness="0" Padding="4,0" MinWidth="0" ToolTip.Tip="Reload plugins" />
          <Button Name="BtnOpenEffectDir" Grid.Column="2" Content="📂" Classes="Flat" Background="Transparent"
          BorderThickness="0" Padding="4,0" MinWidth="0" ToolTip.Tip="Open plugin directory" />
          </Grid>
          <Separator Margin="0,0,0,4" />
          <StackPanel Name="PostFxLayers" />
          <TextBlock Name="PostFxEmpty" Text="No post effects added." FontSize="11"
          HorizontalAlignment="Center" Foreground="#9a9aa6" />
          <TextBlock Classes="section" Text="PERFORMANCE" />
          <Grid ColumnDefinitions="*,Auto">
          <TextBlock Classes="label" Text="Framerate" />
          <TextBlock Name="PerfFps" Grid.Column="1" Classes="value" Text="—" />
          </Grid>
          <Grid ColumnDefinitions="*,Auto">
          <TextBlock Classes="label" Text="Workload size" />
          <TextBlock Name="PerfInvocations" Grid.Column="1" Classes="value" Text="—" />
          </Grid>
          <Grid ColumnDefinitions="*,Auto">
          <TextBlock Classes="label" Text="Iteration speed" />
          <TextBlock Name="PerfSpeed" Grid.Column="1" Classes="value" Text="—" />
          </Grid>
          <Grid ColumnDefinitions="*,Auto">
          <TextBlock Classes="label" Text="Total iterations" />
          <TextBlock Name="PerfTotal" Grid.Column="1" Classes="value" Text="—" />
          </Grid>
          <TextBlock Classes="label" Text="Workgroup count" Margin="0,4,0,2" />
          <NumericUpDown Name="WorkgroupBox" Minimum="16" Maximum="4096" Increment="16" FormatString="0" />
          <TextBlock Classes="section" Text="PLUGINS" />
          <Button Name="BtnEditTransform" Content="Edit transform source" HorizontalAlignment="Stretch"
          Margin="0,0,0,4" />
          <Button Name="BtnEditEffect" Content="Edit effect source" HorizontalAlignment="Stretch"
          Margin="0,0,0,4" />
          <Button Name="BtnReloadPlugins" Content="Reload plugins from disk" HorizontalAlignment="Stretch" />
          <TextBlock Classes="section" Text="EXPORT" />
          <TextBlock Classes="label" Text="Preset" />
          <ComboBox Name="ResolutionPresets" HorizontalAlignment="Stretch" Margin="0,2,0,6" />
          <CheckBox Name="LinkAspect" Content="Link aspect ratio" Margin="0,0,0,4" />
          <Grid ColumnDefinitions="*,8,*">
          <StackPanel Grid.Column="0">
          <TextBlock Classes="label" Text="Width" />
          <NumericUpDown Name="ExportWidth" Minimum="16" Maximum="16384" Increment="16" Value="1920"
          FormatString="0" />
          </StackPanel>
          <StackPanel Grid.Column="2">
          <TextBlock Classes="label" Text="Height" />
          <NumericUpDown Name="ExportHeight" Minimum="16" Maximum="16384" Increment="16" Value="1080"
          FormatString="0" />
          </StackPanel>
          </Grid>
        </StackPanel>
      </ScrollViewer>
    </Border>

    <GridSplitter Name="RightSplitter" Grid.Column="3" Background="#2a2c30" ResizeDirection="Columns" />

    <!-- Viewport over node editor -->
    <Grid Name="ViewportSplit" Grid.Column="2" RowDefinitions="*,4,300">

      <Grid Name="ViewportHost" Grid.Row="0" Background="Black" MinHeight="120">
        <Image Name="Viewport" Stretch="Uniform" RenderOptions.BitmapInterpolationMode="HighQuality" />
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
          <TextBlock Name="OverlayText" Text="Loading…"
                     HorizontalAlignment="Center"
                     Foreground="#8a8a95" FontSize="14" />
          <!-- AnimationPanel.xaml:237 puts a Cancel beside the export progress; without it a
               mis-set render could only be stopped by killing the app. -->
          <Button Name="BtnCancelExport" Content="Cancel" IsVisible="False"
                  HorizontalAlignment="Center" />
        </StackPanel>
        <!--
          ControlHintsPanel.xaml: a 400x200 card, bottom-right with Margin 16, #55555555 on a
          #dd333333 2px border, CornerRadius 15, white Consolas text, IsHitTestVisible=False.

          It matters more than it looks: the WASD/QE + IJKL/UO fly loop is fully implemented
          (StartNavigationTick) but was documented by a single mouse-only line, so a working
          feature was invisible. Gamepad rows are omitted - this port has no gamepad support to
          advertise.
        -->
        <Border Name="HintText" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="16"
                Width="400" Height="200"
                Background="#55555555" BorderBrush="#dd333333" BorderThickness="2"
                CornerRadius="15" IsHitTestVisible="False">
          <Grid Margin="14,10" RowDefinitions="Auto,*" ColumnDefinitions="*,*">
            <TextBlock Grid.Row="0" Grid.ColumnSpan="2" Text="Hint"
                       Foreground="White" FontFamily="monospace" FontSize="13"
                       Margin="0,0,0,8" />

            <StackPanel Grid.Row="1" Grid.Column="0" Spacing="3">
              <TextBlock Text="Movement" Foreground="White" FontFamily="monospace"
                         FontSize="13" Margin="0,0,0,4" />
              <TextBlock Text="w / s     forward / back" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="a / d     left / right" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="q / e     down / up" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="shift     slower" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
            </StackPanel>

            <StackPanel Grid.Row="1" Grid.Column="1" Spacing="3">
              <TextBlock Text="Looking around" Foreground="White" FontFamily="monospace"
                         FontSize="13" Margin="0,0,0,4" />
              <TextBlock Text="i / k     pitch" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="j / l     yaw" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="u / o     roll" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="drag      rotate" Foreground="White"
                         FontFamily="monospace" FontSize="12" Margin="0,6,0,0" />
              <TextBlock Text="middle    orbit" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="right     roll / fov" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
              <TextBlock Text="wheel     focus distance" Foreground="White"
                         FontFamily="monospace" FontSize="12" />
            </StackPanel>
          </Grid>
        </Border>
      </Grid>

      <GridSplitter Name="GraphSplitter" Grid.Row="1" Background="#3a3a44" ResizeDirection="Rows" />

      <TabControl Name="BottomTabs" Grid.Row="2" Padding="0" MinHeight="80">
        <TabItem Header="Node graph">
      <!-- The canonical Editor window is [175px transforms | node map | properties];
           the node-graph tab reproduces the first two of those. -->
      <!--
        The Editor's three columns, each resizable: transforms, the node map, and the
        iterator/connection card. GridSplitters rather than fixed docks.
      -->
      <Grid Name="GraphPanel" ColumnDefinitions="175,4,170,4,*,4,300">

      <c:TransformBrowser Name="TransformBrowserView" Grid.Column="0" Margin="4,4,0,4" />
      <GridSplitter Grid.Column="1" Background="#2a2c30" ResizeDirection="Columns" />

      <!--
        The iterators list has NO canonical counterpart - the Windows UI shows iterators only
        as nodes. It sits beside the graph it mirrors rather than occupying a main-window pane
        that belongs to the tone mapping controls.
      -->
      <Border Name="IteratorsPanel" Grid.Column="2" Margin="4" Padding="6"
              Background="#1c1e21">
        <DockPanel>
          <TextBlock DockPanel.Dock="Top" Classes="section" Text="ITERATORS" Margin="0,0,0,6" />
          <StackPanel DockPanel.Dock="Bottom" Margin="0,8,0,0" Spacing="4">
            <Grid ColumnDefinitions="*,*">
              <Button Name="BtnAddIterator" Grid.Column="0" Content="Add" HorizontalAlignment="Stretch" Margin="0,0,2,0" />
              <Button Name="BtnRemoveIterator" Grid.Column="1" Content="Remove" HorizontalAlignment="Stretch" Margin="2,0,0,0" />
            </Grid>
            <Grid ColumnDefinitions="*,*">
              <Button Name="BtnDuplicate" Grid.Column="0" Content="Duplicate" HorizontalAlignment="Stretch" Margin="0,0,2,0"
                      ToolTip.Tip="Copy this iterator, connected to the original" />
              <Button Name="BtnSplit" Grid.Column="1" Content="Split" HorizontalAlignment="Stretch" Margin="2,0,0,0"
                      ToolTip.Tip="Copy it and halve both base weights" />
            </Grid>
            <Grid ColumnDefinitions="*,*">
              <Button Name="BtnFlipOpacity" Grid.Column="0" Content="Flip opacity" HorizontalAlignment="Stretch" Margin="0,0,2,0" />
              <Button Name="BtnFlipWeight" Grid.Column="1" Content="Flip weight" HorizontalAlignment="Stretch" Margin="2,0,0,0" />
            </Grid>
          </StackPanel>
          <ListBox Name="IteratorList" Background="Transparent" />
        </DockPanel>
      </Border>
      <GridSplitter Grid.Column="3" Background="#2a2c30" ResizeDirection="Columns" />

      <Border Classes="card" Margin="4" Padding="0" Grid.Column="4" ClipToBounds="True">
      <Grid>
        <local:NodeEditor Name="NodeEditorView" />
        <Border VerticalAlignment="Top" HorizontalAlignment="Left" Margin="8"
                Background="#99000000" CornerRadius="4" Padding="8,4" IsHitTestVisible="False">
          <TextBlock Foreground="#a0a0aa" FontSize="11" TextWrapping="Wrap" MaxWidth="360"
                     Text="Node graph — drag empty space: pan  ·  wheel: zoom  ·  drag ══ handle: move node  ·  drag off a node → another: connect  ·  click arrow: edit weight  ·  right-click arrow: disconnect" />
        </Border>
      </Grid>
      </Border>

      <GridSplitter Grid.Column="5" Background="#2a2c30" ResizeDirection="Columns" />

      <!-- EditorWindow.xaml:188 - a 300px card holding the iterator and connection editors. -->
      <Border Name="GraphPropsPanel" Grid.Column="6" Classes="card"
              Margin="4" Padding="6">
        <ScrollViewer VerticalScrollBarVisibility="Visible" AllowAutoHide="False"
                      HorizontalScrollBarVisibility="Disabled">
          <StackPanel Margin="6,0,22,0">
            <TextBlock Classes="section" Text="SELECTED ITERATOR" />
            <TextBlock Name="IteratorName" Classes="label" Text="(none)" Margin="0,0,0,4" />
            <TextBlock Classes="label" Text="Name" />
            <!-- IteratorEditor.xaml pairs the name box with a remove button. -->
            <Grid ColumnDefinitions="*,Auto" Margin="0,2,0,6">
            <TextBox Name="IteratorNameBox" Watermark="(unnamed)" />
            <Button Name="BtnRemoveSelected" Grid.Column="1" Content="🗑️" Margin="4,0,0,0"
            Padding="6" ToolTip.Tip="Remove (Delete)" />
            </Grid>
            <TextBlock Classes="label" Text="Transform" />
            <ComboBox Name="TransformCombo" HorizontalAlignment="Stretch" MaxDropDownHeight="400"
            Margin="0,2,0,6" />
            <!-- Ranges, defaults, increments and tooltips: IteratorViewModel.cs -->
            <c:ValueSlider Name="ItStartWeight" />
            <c:ValueSlider Name="ItWeight" />
            <c:ValueSlider Name="ItOpacity" />
            <c:ValueSlider Name="ItColorIndex" />
            <c:ValueSlider Name="ItColorSpeed" />
            <c:ValueSlider Name="ItMix" />
            <c:ValueSlider Name="ItAdd" />
            <TextBlock Classes="label" Text="Shading mode" Margin="0,6,0,2" />
            <ComboBox Name="ShadingCombo" HorizontalAlignment="Stretch" />

            <!-- Per-transform parameters, built at runtime from the plugin's @Params -->
            <StackPanel Name="IteratorParams" />
            <TextBlock Classes="section" Text="SELECTED CONNECTION" />
            <TextBlock Name="ConnectionName" Classes="label" Text="(none — click an arrow)" />
            <c:ValueSlider Name="ConnectionWeight" IsEnabled="False" />
            <TextBlock Classes="section" Text="PALETTE" />
            <!--
              IteratorEditor.xaml:89-92 embeds a PaletteSlider: the gradient itself, with a
              draggable 30x30 swatch two-way bound to the iterator's ColorIndex. It is how you
              pick a colour BY POINTING AT IT. The port had a read-only strip and a bare 0-1
              number, so choosing a hue was guesswork.
            -->
            <c:PaletteSlider Name="PaletteBar" Margin="0,0,0,6" />
            <TextBlock Name="PaletteName" Classes="label" Text="(none)" Margin="0,0,0,4" />
            <Button Name="BtnLoadPalette" Content="Load palette file…" HorizontalAlignment="Stretch" />
            <!--
              PaletteDialogWindow.xaml renders EVERY palette in the loaded file as its own
              gradient button. A .ugr routinely holds hundreds named pal_0117, pal_0118... so a
              ComboBox of names made choosing one a matter of trying each in turn.
            -->
            <Button Name="BtnPickPalette" Content="Choose palette…" HorizontalAlignment="Stretch"
            Margin="0,6,0,0" IsVisible="False" />
          </StackPanel>
        </ScrollViewer>
      </Border>
      </Grid>
        </TabItem>
        <TabItem Header="Animation">
          <local:AnimationPanel Name="AnimationView" />
        </TabItem>
      </TabControl>

    </Grid>

    </Grid>

  </DockPanel>
</Window>