-
Notifications
You must be signed in to change notification settings - Fork 689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #505. Finishes merge of TileView (previously SplitContainer) #2258
Conversation
Awesome. Can you refactor UICatalogApp to use this as part of the PR? That would add another use/test case. |
UPDATE: have got this working quite nicely now in dd246b7
@tig I took a look at changing UICatalog to use a split container. The simplest solution would be just to put the 2 I looked into how to do it with just 1 line down the middle and have made some progress. The biggest issue with this approach is that the 'Scenarios' caption has to go somewhere. It also ideally has to move with the Splitter when dragged (see video). I have a quite hacky solution using a But I thought I'd show the route I am going down before I put too much work into changing I've not committed the change but put the patch diff below so you can give me some feedback on whether you think this is an improvement? --- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -1,4 +1,4 @@
-´╗┐using NStack;
+using NStack;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -151,9 +151,9 @@ namespace UICatalog {
public MenuItem miIsMouseDisabled;
public MenuItem miHeightAsBuffer;
- public FrameView LeftPane;
+ public FrameView ContentPane;
+ public SplitContainer SplitContainer;
public ListView CategoryListView;
- public FrameView RightPane;
public ListView ScenarioListView;
public StatusItem Capslock;
@@ -201,24 +201,29 @@ namespace UICatalog {
}),
new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
StatusBar.Visible = !StatusBar.Visible;
- LeftPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
- RightPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
+ ContentPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
LayoutSubviews();
SetChildNeedsDisplay();
}),
DriverName,
};
- LeftPane = new FrameView ("Categories") {
+ ContentPane = new FrameView ("Categories") {
X = 0,
Y = 1, // for menu
- Width = 25,
+ Width = Dim.Fill (),
Height = Dim.Fill (1),
CanFocus = true,
Shortcut = Key.CtrlMask | Key.C
};
- LeftPane.Title = $"{LeftPane.Title} ({LeftPane.ShortcutTag})";
- LeftPane.ShortcutAction = () => LeftPane.SetFocus ();
+ ContentPane.Title = $"{ContentPane.Title} ({ContentPane.ShortcutTag})";
+ ContentPane.ShortcutAction = () => ContentPane.SetFocus ();
+
+ SplitContainer = new SplitContainer {
+ Width = Dim.Fill (0),
+ Height = Dim.Fill (0),
+ SplitterDistance = 25
+ };
CategoryListView = new ListView (_categories) {
X = 0,
@@ -229,21 +234,23 @@ namespace UICatalog {
CanFocus = true,
};
CategoryListView.OpenSelectedItem += (a) => {
- RightPane.SetFocus ();
+ ScenarioListView.SetFocus ();
};
CategoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
- LeftPane.Add (CategoryListView);
+ ContentPane.Add (SplitContainer);
- RightPane = new FrameView ("Scenarios") {
- X = 25,
+ SplitContainer.Panel1.Add (CategoryListView);
+
+ Label lblScenarios = new Label ("Scenarios") {
+ X = SplitContainer.SplitterDistance + 1,
Y = 1, // for menu
- Width = Dim.Fill (),
- Height = Dim.Fill (1),
- CanFocus = true,
- Shortcut = Key.CtrlMask | Key.S
};
- RightPane.Title = $"{RightPane.Title} ({RightPane.ShortcutTag})";
- RightPane.ShortcutAction = () => RightPane.SetFocus ();
+ SplitContainer.SplitterMoved += (s, e) => {
+ lblScenarios.X = e.SplitterDistance + 1;
+ Application.Top.BringSubviewToFront (lblScenarios);
+ lblScenarios.SetNeedsDisplay ();
+ Application.Top.SetNeedsDisplay ();
+ };
ScenarioListView = new ListView () {
X = 0,
@@ -255,13 +262,13 @@ namespace UICatalog {
};
ScenarioListView.OpenSelectedItem += ScenarioListView_OpenSelectedItem;
- RightPane.Add (ScenarioListView);
+ SplitContainer.Panel2.Add (ScenarioListView);
KeyDown += KeyDownHandler;
Add (MenuBar);
- Add (LeftPane);
- Add (RightPane);
+ Add (ContentPane);
Add (StatusBar);
+ Add (lblScenarios);
Loaded += LoadedHandler;
@@ -287,7 +294,7 @@ namespace UICatalog {
_isFirstRunning = false;
}
if (!_isFirstRunning) {
- RightPane.SetFocus ();
+ ScenarioListView.SetFocus ();
}
Loaded -= LoadedHandler;
} |
Ok UICatalog is now using the new Splitter pane and it looks good. Its one commit so if we don't think it is an improvement I can just revert dd246b7 I've also marked the PR as no longer draft. Merry Christmas! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a PR to this PR incoming. You'll like it!
Thanks for your code @tig its looking way better now! I've fixed the compilation errors in the unit tests and added support for Wierder still the splitter line seems to jump around when the height is small and you mouse on and off it (see end of this video): |
Here my opinion. I think |
I think we can get the best of both worlds by tapping into the @tig 's code changed |
UPDATE: Ahh just noticed that Height is 0 on that top panel, bet that is the issue. I'm out of time thismorning but can probably crack it from that. I've still got 3 failing tests after your changes @tig. They are all the same and manifest when having horizontal splitter and height 3. The top panel does not render (or is cleared, or something renders over the top of it. I've not been able to track down what the issue is. The positions are all correct according to the Watch window:
|
Have added a 3 way split to the SplitContainerExample which makes this more exciting demo. But it has also revealed an issue with Tab navigation not working. Ill dig into it when I have some time but marked as Draft for now. I can tab fine on the UICatalog home screen but in the example I can only tab into the first split container and not the nested one. |
Thanks for your effort. That great. I think the |
Ok turns out I just needed |
Ok I've done some thinking (dangerous I know) and I think this is doable. We need 2 systems for this to work: Arbitrary Connected Lines DrawerThis would work like a 'back buffer' in in graphics. It would store all the vector lines that are added to it. Then user would call 'Finalize' or 'Merge' or something which would create a 2D array in which all the lines are drawn. Corners would be rendered with the appropriate symbol (T, Crosshair or L) based on the direction of intersecting vectors at that point. The full final frame would then be used for whatever (e.g. drawn to screen). This would be pretty easy to test and develop independently and demoable with a kind of 'drawing' Scenario. Integrated nesting of SplitContainersCurrently a SplitContainer has 2 panels. If we change In layout logic we can use recursion to pass ever reducing EstimateLots of work! but sounds fun! |
Ok started exploring this idea here: https://github.com/tznind/gui.cs/blob/line-drawer/Terminal.Gui/Core/Graphs/StraightLineCanvas.cs |
I'm so in love with this thinking I can't stand it. I wonder if we can do just enough to the current PR to ship it quickly while ensuring we can layer this other stuff in without breaking backwards compat? |
StraightLineCanvas is comming along:
It should be possible but I think i need to work this a bit longer to be sure that the solution will work. Don't want to rename our panels |
You guys are going to be disgusted by this, but for giggles, I asked ChatGPT to write a paneled window manager:
using System;
using System.Collections.Generic;
using Terminal.Gui;
namespace PanelDemo
{
public class PanelManager
{
private List<Panel> panels;
private List<Line> lines;
private Window window;
private int panelSize;
private int lineSize;
private int numPanels;
public PanelManager(Window window, int numPanels, int panelSize, int lineSize)
{
this.window = window;
this.numPanels = numPanels;
this.panelSize = panelSize;
this.lineSize = lineSize;
panels = new List<Panel>();
lines = new List<Line>();
for (int i = 0; i < numPanels; i++)
{
Panel panel = new Panel(new Rect(0, 0, panelSize, window.Frame.Height));
panels.Add(panel);
window.Add(panel);
if (i < numPanels - 1)
{
Line line = new Line(new Rect(panelSize, 0, lineSize, window.Frame.Height));
lines.Add(line);
window.Add(line);
}
}
}
public void ResizePanel(int panelIndex, int newSize)
{
Panel panel = panels[panelIndex];
int oldSize = panel.Frame.Width;
panel.Frame = new Rect(panel.Frame.X, panel.Frame.Y, newSize, panel.Frame.Height);
if (panelIndex < numPanels - 1)
{
Line line = lines[panelIndex];
line.Frame = new Rect(line.Frame.X + newSize - oldSize, line.Frame.Y, line.Frame.Width, line.Frame.Height);
panels[panelIndex + 1].Frame = new Rect(panels[panelIndex + 1].Frame.X + newSize - oldSize, panels[panelIndex + 1].Frame.Y, panels[panelIndex + 1].Frame.Width - newSize + oldSize, panels[panelIndex + 1].Frame.Height);
}
window.Refresh();
}
}
}
Window window = new Window("Panel Demo");
PanelManager panelManager = new PanelManager(window, 3, 50, 2);
// Resize the second panel to be 60 units wide
panelManager.ResizePanel(1, 60); |
Starting to feel like a deep rabbit hole! Not sure this recursive approach is the best. I'll keep digging and see but does feel a bit like I am working against API rather than with it. Starting to feel like a deep rabbit hole!
Not sure this recursive approach is the best. I'll keep digging and see but does feel a bit like I am working against API rather than with it.
```csharp
class SplitPanel : View
View Left
View Right
Pos SplitterDistance
public virtual void LayoutSubviews ()
{
var currentSize = Bounds;
if(HasBorder())
{
currentSize = new Rect(1,1,Bounds.Width,Bounds.Height);
}
LayoutSubviews(this, currentSize);
}
private LayoutSubviews(SplitPanel panel, Rect bounds)
{
var leftSpace = panel.SplitterDistance.Anchor(currentSize)
var rightSpace = bounds.Width - leftSpace;
if(panel.Left is SplitPanel ls)
{
LayoutSubviews(ls,new Rect(bounds.X,bounds.Y, width:leftSpace, height: bounds.Height));
}
else
{
panel.Left.X = bounds.X;
panel.Left.Y = bounds.Y;
panel.Width = leftSpace;
panel.Height = bounds.Height;
}
if(panel.Right is SplitPanel rs)
{
LayoutSubviews(rs,new Rect(bounds.X+leftSpace,bounds.Y, width:rightSpace, height: bounds.Height));
}
else
{
panel.Right.X = bounds.X + leftSpace;
panel.Right.Y = bounds.Y;
panel.Right.Width = rightSpace;
panel.Right.Height = bounds.Height;
}
// TODO: Also position line view
}
// TODO: Override get child views to return from tree
// TODO: How to suppress child layouts?
// TODO: How to add new child views
public override void Redraw (Rect bounds)
{
base.Redraw (bounds);
if (!HasBorder ())
return;
var canvas = new LineCanvas ();
foreach (var l in Subviews.OfType<SplitContainerLineView> ()) {
// TODO: Add to canvas
}
canvas.Draw(this,bounds)
}
}
|
No, what's happening is you are more closer to find the best way to do this. |
Haha thanks for your confidence :) |
Ah right. You have to ensure if there have or not another tile view. If exist and is the last view focused and Tab pressed then must navigate to the first view of the tile view. |
Ok should be working now. I have deleted the entire |
Yet better, let the View base work :-) |
It's working but has a new wrong behavior. The navigation is backwards. Edit: |
Yeah I think it mostly depends what order you split and add to But if you SplitLeft then take the left pane and SplitUp then tab navigation works and feels right. |
Hmn test failures are They are for when you make the splitter lines focusable during runtime. I guess maybe I need to remove the line views and then add them again as focusable or something Strangely it doesn't affect the Scenarios only the tests. |
Well if you want to order always by left top corner to down and right bottom corner, then you have to use |
Yeah don't think I want to do that! feels a bit hacky/risky and I think the current behaviour is ok. Might look into adjusting the |
At runtime you have to set the superview can focus to true before.
If you are adding at load a line view that has CanFocus as true it will also sets the superview CanFocus to true.
Let me say some clarification on this. |
I forgot to remember that solution before, sorry. It's the better and ensured way. |
Cool, so the fix I applied for the |
Well I don't agree. Even it have no children, but it could can get the focus to manage his position. Imagine you want to move the line through the keyboard. For that it needed to get focus first for being receiving keys events. But if the superview |
Ah there is a keypress (Ctrl+F10 by default) that makes the lines focusable so user can tab to them and move them with cursor keys :). In fact it is that toggling that was originally throwing the InvalidArgumentException. I've also seen that even if |
The trick is bellow if it isn't overridden (in this case maybe is better call the Terminal.Gui/Terminal.Gui/Core/View.cs Lines 943 to 953 in 9cfa78a
|
@tznind - same question about v1 v v2 for this. |
Sure, if it saves work I'm all for it
…On Tue, 28 Feb 2023, 01:57 Tig, ***@***.***> wrote:
@tznind <https://github.com/tznind> - same question about v1 v v2 for
this.
—
Reply to this email directly, view it on GitHub
<#2258 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AHO3C5FV7O4DFPIYRZTTGUDWZVLSDANCNFSM6AAAAAATIASQFU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
I hadn't realized this was already in v2. I've fetched and merged v2 into this and now the only changes are the toggle button and tab order fixes. It also targets the v2 branch now. |
Yeah, I needed it for Config Manager so merged it a bit ago. Why does this PR have all the config manager stuff in it as well? |
This PR is now only +102 / −62 after merging Looks like there are only very recent (unmerged) changes now in this branch that are not still on v2. The configuration manager stuff is already in v2. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great stuff. Thanks for doing the dirty work on this one.
Be aware, however, that I have a crazy idea that TileView
may not be needed as a separate view my v2 View
redesign works. We shall see... ;-)
Fixes #505 - Adds new View TileView
TileView can have any number of Tiles. Each tile is seperated with a line which can be moved. Tiles can be added and removed adhoc (see
InsertTile
,RemoveTile
).All Tiles must follow the same
Orientation
. You can achieve grids/ T shapes etc of tiles through nesting. To make this easier you can use the methodbool TrySplitTile(int idx, int panels, out TileView result)
.This PR contains the following sub PR which could be merged seperately
Noteworthy changes
ViewToScreen
public. This is to position ContextMenu when right clicking TabView headers correctly. I have used this method via reflection in TerminalGuiDesigner and don't really see whyScreenToView
is public butViewToScreen
is private.bounds
passed toRedraw
is used for drawing border instead ofBounds
which is always correct. This manifested as a missing border when moving splitter lines (which reapeared after moving cursor or clicking... most strange).ToDos
Visible
property to hide tilesModeled after the WinForms / Windows SplitContainer I've used the same name and public property names (Panel1, Panel2, SplitterDistance etc).
Draft for now as there's a few kinks to work out. Theres a bit of a challenge in supporting both Percent and Absolute splitter distances. Its mostly working for both but needs some thourough testing and to consider the other
Pos
types users could assign toSplitterDistance
.Out of Scope
Pos
. Letting user resize with keyboard/mouse means preserving the PosType is difficult. Its most feasible if we restrict to Percent and Absolute. So we should throwArgumentException
if user setsSplitterDistance
to non percent/absolute (at least for time being).Pull Request checklist:
CTRL-K-D
to automatically reformat your files before committing.dotnet test
before commit///
style comments)