Skip to content

Commit

Permalink
fix(Studio): Properly resize popup-menu depending on available size
Browse files Browse the repository at this point in the history
  • Loading branch information
psyGamer committed Sep 22, 2024
1 parent ef268c4 commit fd4f0f2
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 71 deletions.
2 changes: 1 addition & 1 deletion Studio/CelesteStudio.GTK/CelesteStudio.GTK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<ItemGroup>
<ProjectReference Include="..\CelesteStudio\CelesteStudio.csproj" />
<!-- <PackageReference Include="Eto.Platform.Gtk" Version="2.8.3"/>-->
<!-- <PackageReference Include="Eto.Platform.Gtk" Version="2.8.3"/>-->

<!-- Use a CI-build, since that contains a fix for some issues -->
<PackageReference Include="Eto.Platform.Gtk" Version="2.8.4-ci-20240710.9879198421" />
Expand Down
6 changes: 1 addition & 5 deletions Studio/CelesteStudio.WPF/CelesteStudio.WPF.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>

Expand All @@ -16,10 +16,6 @@
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>

<ItemGroup>
<TrimmableAssembly Include="MyAssembly" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CelesteStudio\CelesteStudio.csproj"/>
<PackageReference Include="Eto.Platform.Wpf" Version="2.8.3"/>
Expand Down
65 changes: 39 additions & 26 deletions Studio/CelesteStudio/Editing/Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -619,38 +619,51 @@ private void Recalc() {
const int menuXOffset = 8;
const int menuYOffset = 7;
const int menuLPadding = 7;
const int menuRPadding = 20;
const int menuRPadding = 7;

float carX = Font.CharWidth() * Document.Caret.Col;
float carY = Font.LineHeight() * (actualToVisualRows[Document.Caret.Row] + 1);

int menuX = (int)(carX + scrollablePosition.X + textOffsetX + menuXOffset);
int menuYBelow = (int)(carY + menuYOffset);
int menuYAbove = (int)Math.Max(carY - Font.LineHeight() - menuYOffset - ActivePopupMenu.ContentHeight, scrollablePosition.Y + menuYOffset);
int menuMaxRight = scrollablePosition.X + scrollableSize.Width - menuRPadding;
int menuMaxW = menuMaxRight - menuX;
int menuMaxHBelow = (int)(scrollablePosition.Y + scrollableSize.Height - Font.LineHeight() - menuYBelow);
int menuMaxHAbove = (int)(scrollablePosition.Y + carY - Font.LineHeight() - menuYOffset - menuYAbove);

// Try moving the menu to the left when there isn't enough space, before having to shrink it
if (menuMaxW < ActivePopupMenu.ContentWidth) {
menuX = (int)Math.Max(menuMaxRight - ActivePopupMenu.ContentWidth, scrollablePosition.X + textOffsetX + menuLPadding);
menuMaxW = menuMaxRight - menuX;
}

// Chose above / below caret depending on which provides more height. Default to below
int menuY, menuMaxH;
if (Math.Min(ActivePopupMenu.ContentHeight, menuMaxHBelow) >= Math.Min(ActivePopupMenu.ContentHeight, menuMaxHAbove)) {
menuY = menuYBelow;
menuMaxH = menuMaxHBelow;
} else {
menuY = menuYAbove;
menuMaxH = menuMaxHAbove;
int menuX, menuY;

void UpdateMenuW() {
menuX = (int)(carX + scrollablePosition.X + textOffsetX + menuXOffset);
int menuMaxRight = scrollablePosition.X + scrollableSize.Width - menuRPadding - (ActivePopupMenu.VScrollBarVisible ? Studio.ScrollBarSize : 0);
int menuMaxW = menuMaxRight - menuX;

// Try moving the menu to the left when there isn't enough space, before having to shrink it
if (menuMaxW < ActivePopupMenu.ContentWidth) {
menuX = (int)Math.Max(menuMaxRight - ActivePopupMenu.ContentWidth, scrollablePosition.X + textOffsetX + menuLPadding);
menuMaxW = menuMaxRight - menuX;
}

ActivePopupMenu.ContentWidth = Math.Min(ActivePopupMenu.ContentWidth, menuMaxW);
}
void UpdateMenuH() {
int menuYBelow = (int)(carY + menuYOffset);
int menuYAbove = (int)Math.Max(carY - Font.LineHeight() - menuYOffset - ActivePopupMenu.ContentHeight, scrollablePosition.Y + menuYOffset);

int menuMaxHBelow = (int)(scrollablePosition.Y + scrollableSize.Height - Font.LineHeight() - menuYBelow) - (ActivePopupMenu.HScrollBarVisible ? Studio.ScrollBarSize : 0);
int menuMaxHAbove = (int)(scrollablePosition.Y + carY - Font.LineHeight() - menuYOffset - menuYAbove);

// Chose above / below caret depending on which provides more height. Default to below
int menuMaxH;
if (Math.Min(ActivePopupMenu.ContentHeight, menuMaxHBelow) >= Math.Min(ActivePopupMenu.ContentHeight, menuMaxHAbove)) {
menuY = menuYBelow;
menuMaxH = menuMaxHBelow;
} else {
menuY = menuYAbove;
menuMaxH = menuMaxHAbove;
}

ActivePopupMenu.ContentHeight = Math.Min(ActivePopupMenu.ContentHeight, menuMaxH);
}

// Both depend on each-other, so one needs to be updated twice
UpdateMenuW();
UpdateMenuH();
UpdateMenuW();

// Set height first, to account for a potential scroll bar
ActivePopupMenu.ContentHeight = Math.Min(ActivePopupMenu.ContentHeight, menuMaxH);
ActivePopupMenu.ContentWidth = Math.Min(ActivePopupMenu.ContentWidth, menuMaxW);
ActivePopupMenu.Recalc();
pixelLayout.Move(ActivePopupMenu, menuX, menuY);
}
Expand Down
73 changes: 34 additions & 39 deletions Studio/CelesteStudio/Editing/PopupMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,6 @@ public record Entry {
/// Spacing between the longest DisplayText and ExtraText of entries in characters
private const int DisplayExtraPadding = 2;

private int ScrollBarWidth {
get {
bool scrollBarVisible = Height < ContentHeight && Height > 0;
if (!scrollBarVisible) {
return 0;
}

if (Eto.Platform.Instance.IsWpf) {
return 17;
}
if (Eto.Platform.Instance.IsGtk) {
return 17; // This probably relies on the GTK theme, but being slight off isn't too big of an issue
}
if (Eto.Platform.Instance.IsMac) {
return 15;
}
return 0;
}
}

private sealed class ContentDrawable : Drawable {
private readonly PopupMenu menu;

Expand Down Expand Up @@ -108,7 +88,9 @@ protected override void OnPaint(PaintEventArgs e) {
entry.ExtraText);
}

Size = new(menu.ContentWidth, menu.ContentHeight);
if (Eto.Platform.Instance.IsGtk) {
Size = new(menu.ContentWidth, menu.ContentHeight);
}

base.OnPaint(e);
}
Expand Down Expand Up @@ -192,23 +174,22 @@ public int SelectedEntry {
}
}

public int ContentWidth {
set => Width = Math.Max(0, value);
get {
if (shownEntries.Length == 0) {
return 0;
}
// We update our size, but reading it won't give the updated value immediatly.
// However we need the updated size to properly calculate other things.
private Size actualSize = new(0, 0);

var font = FontManager.PopupFont;
int maxDisplayLen = shownEntries.Select(entry => entry.DisplayText.Length).Aggregate(Math.Max);
int maxExtraLen = shownEntries.Select(entry => entry.ExtraText.Length).Aggregate(Math.Max);
public bool VScrollBarVisible => actualSize.Height < contentHeight && actualSize.Height > 0;
public bool HScrollBarVisible => actualSize.Width < contentWidth && actualSize.Width > 0;

return (int)(font.CharWidth() * (maxDisplayLen + DisplayExtraPadding + maxExtraLen) + Settings.Instance.Theme.PopupMenuEntryHorizontalPadding * 2.0f + Settings.Instance.Theme.PopupMenuBorderPadding * 2 + ScrollBarWidth);
}
private int contentWidth;
public int ContentWidth {
set => Width = actualSize.Width = Math.Max(0, value + (VScrollBarVisible ? Studio.ScrollBarSize : 0));
get => contentWidth;
}
private int contentHeight;
public int ContentHeight {
set => Height = Math.Max(0, value);
get => shownEntries.Length * EntryHeight + Settings.Instance.Theme.PopupMenuBorderPadding * 2;
set => Height = actualSize.Height = Math.Max(0, value + (HScrollBarVisible ? Studio.ScrollBarSize : 0));
get => contentHeight;
}

public int EntryHeight => (int)(FontManager.PopupFont.LineHeight() + Settings.Instance.Theme.PopupMenuEntryVerticalPadding * 2.0f + Settings.Instance.Theme.PopupMenuEntrySpacing);
Expand All @@ -227,17 +208,31 @@ public PopupMenu() {
public void Recalc() {
shownEntries = entries.Where(entry => string.IsNullOrEmpty(entry.SearchText) || entry.SearchText.StartsWith(filter, StringComparison.InvariantCultureIgnoreCase)).ToArray();
if (shownEntries.Length == 0) {
contentWidth = 0;
contentHeight = 0;
Visible = false;
return;
}

selectedEntry = Math.Clamp(selectedEntry, 0, shownEntries.Length - 1);

// The +1 is a hack-fix for GTK.
// If the content isn't large enough to require a scrollbar, GTK will just have the background be black instead of transparent.
// Even weirder, once it was scrollable once, the black background will never come back...
// The proper size is set at the end of ContentDrawable.Paint()
drawable.Size = new(ContentWidth, ContentHeight + 1);
// Calculate content bounds. Calculate height first to account for scroll bar
contentHeight = shownEntries.Length * EntryHeight + Settings.Instance.Theme.PopupMenuBorderPadding * 2;

var font = FontManager.PopupFont;
int maxDisplayLen = shownEntries.Select(entry => entry.DisplayText.Length).Aggregate(Math.Max);
int maxExtraLen = shownEntries.Select(entry => entry.ExtraText.Length).Aggregate(Math.Max);

contentWidth = (int)(font.CharWidth() * (maxDisplayLen + DisplayExtraPadding + maxExtraLen) + Settings.Instance.Theme.PopupMenuEntryHorizontalPadding * 2.0f + Settings.Instance.Theme.PopupMenuBorderPadding * 2);

if (Eto.Platform.Instance.IsGtk) {
// If the content isn't large enough to require a scrollbar, GTK will just have the background be black instead of transparent.
// Even weirder, once it was scrollable once, the black background will never come back...
// The proper size is set at the end of ContentDrawable.Paint()
drawable.Size = new(ContentWidth, ContentHeight + 1);
} else {
drawable.Size = new(ContentWidth, ContentHeight);
}
drawable.Invalidate();
}

Expand Down
16 changes: 16 additions & 0 deletions Studio/CelesteStudio/Studio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ static Studio() {
? $"<Scratch> - Studio {Version}"
: $"{Editor.Document.FileName}{(Editor.Document.Dirty ? "*" : string.Empty)} - Studio {Version} {Editor.Document.FilePath}";

/// Size of scroll bars, depending on the current platform
public static int ScrollBarSize {
get {
if (Eto.Platform.Instance.IsWpf) {
return 17;
}
if (Eto.Platform.Instance.IsGtk) {
return 17; // This probably relies on the GTK theme, but being slight off isn't too big of an issue
}
if (Eto.Platform.Instance.IsMac) {
return 15;
}
return 0;
}
}

public Studio(string[] args, Action<Window> windowCreationCallback) {
Instance = this;
Icon = Assets.AppIcon;
Expand Down

0 comments on commit fd4f0f2

Please sign in to comment.