Skip to content
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

Struggling to understand the new TabStop functionality along with new Scrolling behavior #3831

Open
dasien opened this issue Nov 19, 2024 · 30 comments
Labels
design Issues regarding Terminal.Gui design (bugs, guidelines, debates, etc...)
Milestone

Comments

@dasien
Copy link
Contributor

dasien commented Nov 19, 2024

Hello,

I recently converted my application Retrowarden to the v2 Terminal GUI. Many improvements so i am very excited to see v2 continue to develop.

One major area of confusion i have is tab behavior and scrolling. I removed all the old ScrollBar code i had, but i am struggling to make scrolling work in the application. It would seem that in order to be compliant with "Keyboard Required" tenet, the appropriate place for me to handle the need to scroll to the control which is about to receive focus is the AdvanceFocus method? I want to make sure that the intention is for the developer to manage this via AdvanceFocus (or some other method/event), and not have it just be default behavior of the underlying system to make sure that the control with focus is visible. If the intention is for the developer to manage making sure the control that receives focus is viewable, I would really appreciate an example if possible, as i am struggling with making this work in v2.

As a precursor to that, however, is an understanding of how the new TabStop functionality is supposed to work. I have gone through my application's Views and set TabStop appropriately for all of them (I think). I have added them in the order in which i wanted them to be tabbed, even though there is no longer a way to set tab order directly. I still do not understand how the underlying system is
determining the order of tab navigation for views. Whenever i Tab through the controls, many are skipped and the order is not Top Left to Bottom Right as you would expect. To take advantage of the fact that my application has several common controls across detail input forms, i created the following structure.

+----------------------------+
+ Common Header Controls     +
+ (part of base class)       +
+----------------------------+
+----------------------------+
+ Custom Detail View         +
+ (part of derived class)    +
+----------------------------+
+----------------------------+
+ Common Footer Controls     +
+ (part of base class)       +
+----------------------------+

At runtime, depending on which type of item the user wants to add/edit, i swap in a "Detail View" of the appropriate type in the middle of the common header and footer controls. The Views contained in this Detail View never receive focus via a Tab key press. I suppose i could just create separate views for each item type and duplicate the common controls to get rid of the hierarchy, but i would prefer not to do that. I had a similar problem in V1, and the solution was for me to sprinkle in some calls to FocusFirst along with setting the TabOrder custom after the Views had all been assigned. I cannot set TabOrder any longer (the code is currently commented out in my project), so i am not sure how to get the application to consistently tab through the controls in the order that a user would expect. Even for those that it tabs to, the order kind of bounces around and is not in an order a user would expect.

Describe the solution you'd like
I would like to either understand how to make the current v2 tabbing system work, or have fine-grained control over the order in which the controls i place on the screen are tabbed. On the surface, the v1 solution of TabOrder made sense, but the underlying view composition didn't seem to respect those when you set them before the views were added at runtime, hence the need for me to go back with a custom method and override whatever the order was. I appreciate the new TabStop style, but without any way to directly influence the order in which the controls get focus, I don't know how to make that work.

For scrolling, just an example of how it is intended to work with keyboard events would be helpful. I don't see any other method in v2 that would be the appropriate place to check to see if a control is visible based on is x/y relative to the ViewPort other than AdvanceFocus (e.g. no Enter or GotFocus). An example that shows the intended behavior for an application to check a View's position relative to the visible part of the screen and what to call to advance the scrolling so that the control is visible would be great.

Thank you for all the great work!

@tig
Copy link
Collaborator

tig commented Nov 19, 2024

As a precursor to that, however, is an understanding of how the new TabStop functionality is supposed to work. I have gone through my application's Views and set TabStop appropriately for all of them (I think). I have added them in the order in which i wanted them to be tabbed, even though there is no longer a way to set tab order directly. I still do not understand how the underlying system is determining the order of tab navigation for views. Whenever i Tab through the controls, many are skipped and the order is not Top Left to Bottom Right as you would expect. To take advantage of the fact that my application has several common controls across detail input forms, i created the following structure.

First, thank you for posting all this! I'm eager to both help you and ensure that the new design actually works and makes devs happy. It's entirely possible you've found real issues that need to be addressed!!

The tab order in v2 is very simple: The order is the order in which the Views exist in the View.Subviews list.

+----------------------------+
+ Common Header Controls     +
+ (part of base class)       +
+----------------------------+
+----------------------------+
+ Custom Detail View         +
+ (part of derived class)    +
+----------------------------+
+----------------------------+
+ Common Footer Controls     +
+ (part of base class)       +
+----------------------------+

I'm a bit confused by the base/derived stuff going on here. Perhaps your issue is you should be using composition instead of inheritance. I'd have:

  • appView - The main container View. It would have n (n >= 2) subviews, Added in this order:
  • header = MyHeaderVIew()
  • detailview1 = SomeView()
  • ...
  • footer = MyFooterView()

All focusable views must have CanFocus = true and in the above if any focusable view has TabStop = TabBehavior.TabStop things should just work.

@tig
Copy link
Collaborator

tig commented Nov 19, 2024

For scrolling, just an example of how it is intended to work with keyboard events would be helpful.

Please see #3820, which will likely be merged to v2_develop today. It completely revamps ScrollBar and scrolling and the new examples (and scrolling.md) should make this all clear. If it doesn't, then I have more work to do ;-).

I don't see any other method in v2 that would be the appropriate place to check to see if a control is visible based on is x/y relative to the ViewPort other than AdvanceFocus (e.g. no Enter or GotFocus). An example that shows the intended behavior for an application to check a View's position relative to the visible part of the screen and what to call to advance the scrolling so that the control is visible would be great.

Why do you need to know if a control is visible or not?

Assuming there's actually a good reason, the algo would be something like this:

bool isVisible = Viewport.Contains(isVisibleView.Frame.Location) && Viewport.Contains(isVisibleView.Location with { X = isVisibleView.X + isVisibleView.Width, Y = isVisibleView.Y + isVisibleView.Height);

This would be true IFF the entire subview was visible. The algo for determining if just a part is visible is much tricker. I'd use the new Region class for this.

HOWEVER, there is a TON of complexity in doing something USEFUL with this info. That complexity is what has prevented me from investing in an API. It's a really hard problem to make it completely general purpose.

... which brings me back to WHY? Why do you think you need such functionality?

@tig tig added the design Issues regarding Terminal.Gui design (bugs, guidelines, debates, etc...) label Nov 19, 2024
@tig tig added this to the V2 Release milestone Nov 19, 2024
@dasien
Copy link
Contributor Author

dasien commented Nov 19, 2024

For scrolling, just an example of how it is intended to work with keyboard events would be helpful.

Please see #3820, which will likely be merged to v2_develop today. It completely revamps ScrollBar and scrolling and the new examples (and scrolling.md) should make this all clear. If it doesn't, then I have more work to do ;-).

I don't see any other method in v2 that would be the appropriate place to check to see if a control is visible based on is x/y relative to the ViewPort other than AdvanceFocus (e.g. no Enter or GotFocus). An example that shows the intended behavior for an application to check a View's position relative to the visible part of the screen and what to call to advance the scrolling so that the control is visible would be great.

Why do you need to know if a control is visible or not?

Assuming there's actually a good reason, the algo would be something like this:

bool isVisible = Viewport.Contains(isVisibleView.Frame.Location) && Viewport.Contains(isVisibleView.Location with { X = isVisibleView.X + isVisibleView.Width, Y = isVisibleView.Y + isVisibleView.Height);

This would be true IFF the entire subview was visible. The algo for determining if just a part is visible is much tricker. I'd use the new Region class for this.

HOWEVER, there is a TON of complexity in doing something USEFUL with this info. That complexity is what has prevented me from investing in an API. It's a really hard problem to make it completely general purpose.

... which brings me back to WHY? Why do you think you need such functionality?

thank you for the reply! I don't need to know if the control is visible or not if the underlying scrolling mechanism detects that a control which just received focus is not visible (because it is above/below the current visible area of the screen, and automatically scrolls to make that control visible. I do need to know that if i have to control the scrolling myself, so i know whether or not i needs to scroll the view so that the control is visible.

@tig
Copy link
Collaborator

tig commented Nov 19, 2024

Can you help me get to the point where I can run your current code such that I get real data so i can test? I've never used bitwarden....

image

@dasien
Copy link
Contributor Author

dasien commented Nov 19, 2024

As a precursor to that, however, is an understanding of how the new TabStop functionality is supposed to work. I have gone through my application's Views and set TabStop appropriately for all of them (I think). I have added them in the order in which i wanted them to be tabbed, even though there is no longer a way to set tab order directly. I still do not understand how the underlying system is determining the order of tab navigation for views. Whenever i Tab through the controls, many are skipped and the order is not Top Left to Bottom Right as you would expect. To take advantage of the fact that my application has several common controls across detail input forms, i created the following structure.

First, thank you for posting all this! I'm eager to both help you and ensure that the new design actually works and makes devs happy. It's entirely possible you've found real issues that need to be addressed!!

The tab order in v2 is very simple: The order is the order in which the Views exist in the View.Subviews list.

+----------------------------+
+ Common Header Controls     +
+ (part of base class)       +
+----------------------------+
+----------------------------+
+ Custom Detail View         +
+ (part of derived class)    +
+----------------------------+
+----------------------------+
+ Common Footer Controls     +
+ (part of base class)       +
+----------------------------+

I'm a bit confused by the base/derived stuff going on here. Perhaps your issue is you should be using composition instead of inheritance. I'd have:

* `appView` - The main container View. It would have n (n >= 2) subviews, `Add`ed in this order:

* `header = MyHeaderVIew()`

* `detailview1 = SomeView()`

* ...

* `footer = MyFooterView()`

All focusable views must have CanFocus = true and in the above if any focusable view has TabStop = TabBehavior.TabStop things should just work.

Thank you for that insight. I will think about using a composite methodology to construct the overall detail view. Basically, each detail view "inherits" some common properties from the parent. For example, every item which can be saved in the application has an "Item Name", so i have a base View with a Label and TextField for storing the item name. The base View also has the "Save" and "Cancel" buttons.

I need to think about where i might use that common view type (the base class) in other parts of the application, but a composite pattern might work.

@tig
Copy link
Collaborator

tig commented Nov 19, 2024

I just ported your code to the latest v2_develop. Would you like a PR?

Not sure everything is working because I can't get past login, but it should help.

@tig
Copy link
Collaborator

tig commented Nov 19, 2024

Before you go rearchitecting, lets get to the point where I can actually run the thing and poke around. This is a great opportunity for me to ensure I understand how a "real" dev (not one of us maintainers) does things naturally!

@dasien
Copy link
Contributor Author

dasien commented Nov 19, 2024

Can you help me get to the point where I can run your current code such that I get real data so i can test? I've never used bitwarden....

image

Hi - sure, i have some test credentials that you can use. The app is in a tricky state right now, but you should be able to see the two issues on one of the detail forms. What you should do it go to Vault - Connect and put these credentials in "[email protected]" with password "otGZkrC7kWPd6g" (both without quotes). You should then see the vault screen loaded with some test items. The top one should be highlighted and you can press 'F3' to edit that item. Since that is a item of type "Login", the detail form that is loaded is a LoginDetailView. You will see that the first control to get focus is the combo box for "Folder". Pressing Tab will move the focus back to Item Name text field, etc. After 3-4 tabs, the cursor will disappear. That is because Focus has skipped all the middle controls and gone down past the bottom of the view port, to the button for "New Custom Field". Then it goes to "Save", then "Cancel", then back up to the "Folder" combobox. All of the controls in the middle are never given focus.

@dasien
Copy link
Contributor Author

dasien commented Nov 19, 2024

I just ported your code to the latest v2_develop. Would you like a PR?

Not sure everything is working because I can't get past login, but it should help.

Sure that would be great!

@BDisp BDisp mentioned this issue Nov 19, 2024
9 tasks
@tig
Copy link
Collaborator

tig commented Nov 20, 2024

Can you help me get to the point where I can run your current code such that I get real data so i can test? I've never used bitwarden....
image

Hi - sure, i have some test credentials that you can use. The app is in a tricky state right now, but you should be able to see the two issues on one of the detail forms. What you should do it go to Vault - Connect and put these credentials in "[email protected]" with password "otGZkrC7kWPd6g" (both without quotes). You should then see the vault screen loaded with some test items. The top one should be highlighted and you can press 'F3' to edit that item. Since that is a item of type "Login", the detail form that is loaded is a LoginDetailView. You will see that the first control to get focus is the combo box for "Folder". Pressing Tab will move the focus back to Item Name text field, etc. After 3-4 tabs, the cursor will disappear. That is because Focus has skipped all the middle controls and gone down past the bottom of the view port, to the button for "New Custom Field". Then it goes to "Save", then "Cancel", then back up to the "Folder" combobox. All of the controls in the middle are never given focus.

I now have a bitwarden account and I've installed the Windows bitwarden client. What file do I point your app at when it first starts and shows the Open File Dialog?

@dasien
Copy link
Contributor Author

dasien commented Nov 20, 2024

haha sorry about that - forgot about the setup part. The file is bw.exe. you need to download the CLI application from Bitwarden Help CLI

@tznind
Copy link
Collaborator

tznind commented Nov 20, 2024

haha sorry about that - forgot about the setup part. The file is bw.exe. you need to download the CLI application from Bitwarden Help CLI

With the new FileDialog you can actually place a restriction on what user can pick

image

    class BwDotExe : IAllowedType
    {
        /// <inheritdoc />
        public bool IsAllowed (string path)
        {
            return Path.GetFileName (path).Equals ("bw.exe");
        }

        /// <inheritdoc />
        public override string ToString () { return "bw.exe"; }
    }
    private void CreateDialog ()
    {
        var ofd = new FileDialog ()
        {
            MustExist = true,
            AllowedTypes = [new BwDotExe ()]
        };
        Application.Run (ofd);

        if (!ofd.Canceled)
        {
            MessageBox.Query ("you picked", ofd.Path, "Ok");
        }

@dasien
Copy link
Contributor Author

dasien commented Nov 21, 2024

haha sorry about that - forgot about the setup part. The file is bw.exe. you need to download the CLI application from Bitwarden Help CLI

With the new FileDialog you can actually place a restriction on what user can pick

image

    class BwDotExe : IAllowedType
    {
        /// <inheritdoc />
        public bool IsAllowed (string path)
        {
            return Path.GetFileName (path).Equals ("bw.exe");
        }

        /// <inheritdoc />
        public override string ToString () { return "bw.exe"; }
    }
    private void CreateDialog ()
    {
        var ofd = new FileDialog ()
        {
            MustExist = true,
            AllowedTypes = [new BwDotExe ()]
        };
        Application.Run (ofd);

        if (!ofd.Canceled)
        {
            MessageBox.Query ("you picked", ofd.Path, "Ok");
        }

Thank you for that suggestion - I will integrate that!

@tig
Copy link
Collaborator

tig commented Nov 21, 2024

What font do you have in your Terminal setup?

@dasien
Copy link
Contributor Author

dasien commented Nov 21, 2024

What font do you have in your Terminal setup?

Cascadia Mono - 12pt

@tig
Copy link
Collaborator

tig commented Nov 21, 2024

@tznind - I'm trying to figure out why you're not getting the right glyphs for buttons.

With that font, I get:

image

@tig
Copy link
Collaborator

tig commented Nov 21, 2024

I was able to login once. Now each time I run the app and try to login I get

image

@dasien
Copy link
Contributor Author

dasien commented Nov 21, 2024

I was able to login once. Now each time I run the app and try to login I get

image

I am sorry about that. That will happen if the app crashes - I haven't fixed that yet. For now, you can go to a command prompt and type bw logout and that should log you out.

Pressing escape to exit the app also doesn't fire the logic to make sure the user session is cleaned up.

To properly close the application choose Vault --> Quit

@tig
Copy link
Collaborator

tig commented Nov 21, 2024

@dasien perhaps this will be easier with a smaller sample app? Could you whip up an app that uses your infrastructure that demos the focus/scrolling you want to achieve?

One thing I noticed in your code is you use a ton of absolute layout, where you' are specifying absolute values for locations/sizes of views. You will simplify your overall solution and reduce a ton of fragility by changing to using computed layout.

@dasien
Copy link
Contributor Author

dasien commented Nov 21, 2024

@tig hmm let me see if I can just get some dummy data loaded in those detail views and make one of them a separate project. Once you are logged in, it should just be a matter of pressing F3 to edit the first selected item in the list view and from there the scrolling and tabbing behavior is apparent.

I do use computed layout some on the main form, where I have very few control which end up filling the parent form. Where I have a lot of input controls, however, I am not sure that computed layout is going to be helpful, but I could experiment with that.

Are you having issues getting to the detail form now?

@dasien
Copy link
Contributor Author

dasien commented Nov 21, 2024

I am going to create a "Debug" VaultRepository so that you can put the application into a mode where it doesn't try to make CLI calls and just returns some default data. I tried to just extract the detail forms but there is just too much complexity there.

Give me the evening to create this and the way to put the application in Debug mode and that should make it much easier to not have to worry about the CLI/Bitwarden parts and just navigate around the UI.

@dasien
Copy link
Contributor Author

dasien commented Nov 22, 2024

@tig Ok i pushed an update that makes it so you don't need to use the CLI. In the Program class, you can pass a boolean value to MainView that will put the application in debug mode. In this mode, it will not try to login or fetch actual data from a vault, it will just load some static data. You can't save anything in this state, but this should get the application to where you can see the scrolling and tab behavior. When you do Vault -> Connect, you can put any values in for your login and you should then see the vault contents in the ListView. Press F3 to edit one of the items and a detail form will come up where you can see the first field doesn't have focus and tabbing bounces the focus around. Scrolling to the currently focused control is also not happening.

I will clean up this debug mode so things can be temporarily written, but this should be enough to show what we are trying to see.

@dasien
Copy link
Contributor Author

dasien commented Nov 25, 2024

As a precursor to that, however, is an understanding of how the new TabStop functionality is supposed to work. I have gone through my application's Views and set TabStop appropriately for all of them (I think). I have added them in the order in which i wanted them to be tabbed, even though there is no longer a way to set tab order directly. I still do not understand how the underlying system is determining the order of tab navigation for views. Whenever i Tab through the controls, many are skipped and the order is not Top Left to Bottom Right as you would expect. To take advantage of the fact that my application has several common controls across detail input forms, i created the following structure.

First, thank you for posting all this! I'm eager to both help you and ensure that the new design actually works and makes devs happy. It's entirely possible you've found real issues that need to be addressed!!

The tab order in v2 is very simple: The order is the order in which the Views exist in the View.Subviews list.

+----------------------------+
+ Common Header Controls     +
+ (part of base class)       +
+----------------------------+
+----------------------------+
+ Custom Detail View         +
+ (part of derived class)    +
+----------------------------+
+----------------------------+
+ Common Footer Controls     +
+ (part of base class)       +
+----------------------------+

I'm a bit confused by the base/derived stuff going on here. Perhaps your issue is you should be using composition instead of inheritance. I'd have:

  • appView - The main container View. It would have n (n >= 2) subviews, Added in this order:
  • header = MyHeaderVIew()
  • detailview1 = SomeView()
  • ...
  • footer = MyFooterView()

All focusable views must have CanFocus = true and in the above if any focusable view has TabStop = TabBehavior.TabStop things should just work.

I never really answered this, but this is effectively what is happening. The "base" view has the header and footer controls and a variable called "_subView" that is assigned the appropriate detail view.

As a precursor to that, however, is an understanding of how the new TabStop functionality is supposed to work. I have gone through my application's Views and set TabStop appropriately for all of them (I think). I have added them in the order in which i wanted them to be tabbed, even though there is no longer a way to set tab order directly. I still do not understand how the underlying system is determining the order of tab navigation for views. Whenever i Tab through the controls, many are skipped and the order is not Top Left to Bottom Right as you would expect. To take advantage of the fact that my application has several common controls across detail input forms, i created the following structure.

First, thank you for posting all this! I'm eager to both help you and ensure that the new design actually works and makes devs happy. It's entirely possible you've found real issues that need to be addressed!!

The tab order in v2 is very simple: The order is the order in which the Views exist in the View.Subviews list.

+----------------------------+
+ Common Header Controls     +
+ (part of base class)       +
+----------------------------+
+----------------------------+
+ Custom Detail View         +
+ (part of derived class)    +
+----------------------------+
+----------------------------+
+ Common Footer Controls     +
+ (part of base class)       +
+----------------------------+

I'm a bit confused by the base/derived stuff going on here. Perhaps your issue is you should be using composition instead of inheritance. I'd have:

  • appView - The main container View. It would have n (n >= 2) subviews, Added in this order:
  • header = MyHeaderVIew()
  • detailview1 = SomeView()
  • ...
  • footer = MyFooterView()

All focusable views must have CanFocus = true and in the above if any focusable view has TabStop = TabBehavior.TabStop things should just work.

Circling back to this. I think I have solved 1 of the 3 problems I had. The "Custom Detail View" is now receiving tabs. The issue was in my code. I was not setting CanFocus = true on the top level custom view.

I still have two issues - one is the tab order of controls and the other is scrolling.

For the first one, "The tab order in v2 is very simple: The order is the order in which the Views exist in the View.Subviews list." - Ok that makes sense, but that makes the question then, "What causes that order to be what it is?" On my View I add two labels, which are marked as CanFocus = false. Then I add a TextField with CanFocus = true and then a ComboBox also with CanFocus = true. There are more controls added, but for the purpose of this question we don't need to look beyond these (I don't think).

When the View loads, the ComboBox has focus and is listed as the [0]th element in the parent View list of SubViews, so I am not sure why that is happening. I can put a hack in and just set the HasFocus property of the TextField to true, but the underlying order is still wrong, so pressing Tab from that field doesn't advance to the next field (which should be the ComboBox according to how I added them to the parent View and how the controls are laid out visually).

The 3rd problem is the scrolling to a control with focus so that it is in the visible part of the View and I haven't made any progress on when to check for that, etc. I will have an update in a bit that fixes a little more tab order stuff so that all the controls are assigned to the View in the order I want them to be tabbed through.

@dasien
Copy link
Contributor Author

dasien commented Nov 25, 2024

I created a simple test view to illustrate the issue i am having with the ComboBox getting the focus when the form loads and the tab order being non-intuitive because of how things are ordered in SubViews. In this example, even though the controls are added to the parent in the order Label, TextField, ComboBox, TextField, the order in the SubViews arrary is ComboBox, Label, TextField, TextField, so the tabbing starts with the ComboBox, then goes to the first TextField, then to the next TextField.

Visually, it should go TextField (upper left focusable control), then ComboBox, then the next TextField.

This is using 2.0.0-v2-develop.2329

        
        private Terminal.Gui.Label label;
        
        private Terminal.Gui.TextField textField;
        
        private Terminal.Gui.ComboBox comboBox;
        
        private Terminal.Gui.TextField textField2;

        public TestView() 
        {
            InitializeComponent();
        }
        
        private void InitializeComponent() {
            this.textField2 = new Terminal.Gui.TextField();
            this.comboBox = new Terminal.Gui.ComboBox();
            this.textField = new Terminal.Gui.TextField();
            this.label = new Terminal.Gui.Label();
            this.Width = Dim.Fill(0);
            this.Height = Dim.Fill(0);
            this.X = 0;
            this.Y = 0;
            this.Visible = true;
            this.Arrangement = Terminal.Gui.ViewArrangement.Fixed;
            this.TextAlignment = Terminal.Gui.Alignment.Start;
            this.label.Width = Dim.Auto();
            this.label.Height = 1;
            this.label.X = 8;
            this.label.Y = 4;
            this.label.Visible = true;
            this.label.Arrangement = Terminal.Gui.ViewArrangement.Fixed;
            this.label.Data = "label";
            this.label.Text = "Heya";
            this.label.TextAlignment = Terminal.Gui.Alignment.Start;
            this.Add(this.label);
            this.textField.Width = 17;
            this.textField.Height = 7;
            this.textField.X = 14;
            this.textField.Y = 4;
            this.textField.Visible = true;
            this.textField.Arrangement = Terminal.Gui.ViewArrangement.Fixed;
            this.textField.Secret = false;
            this.textField.Data = "textField";
            this.textField.Text = "Heya";
            this.textField.TextAlignment = Terminal.Gui.Alignment.Start;
            this.Add(this.textField);
            this.comboBox.Width = 29;
            this.comboBox.Height = 2;
            this.comboBox.X = 38;
            this.comboBox.Y = 4;
            this.comboBox.Visible = true;
            this.comboBox.Arrangement = Terminal.Gui.ViewArrangement.Fixed;
            this.comboBox.Data = "comboBox";
            this.comboBox.Text = "";
            this.comboBox.TextAlignment = Terminal.Gui.Alignment.Start;
            this.Add(this.comboBox);
            this.textField2.Width = 21;
            this.textField2.Height = 1;
            this.textField2.X = 14;
            this.textField2.Y = 6;
            this.textField2.Visible = true;
            this.textField2.Arrangement = Terminal.Gui.ViewArrangement.Fixed;
            this.textField2.Secret = false;
            this.textField2.Data = "textField2";
            this.textField2.Text = "Heya";
            this.textField2.TextAlignment = Terminal.Gui.Alignment.Start;
            this.Add(this.textField2);
        }
    }

@tig
Copy link
Collaborator

tig commented Nov 25, 2024

ComboBox is a hack. And does hacky things. E.g.:

image

It should not be re-ordering the Superview's list of subviews!

As part of fixing

I want to refactor ComboBox to get rid of it's hackyness.

It may be possible to implement a short-term fix though. Opening a new issue to track...

@dasien
Copy link
Contributor Author

dasien commented Nov 25, 2024

Thank you for that update - good to know I wasn't going crazy with adding things and them not being in the order in which I added them! I cleaned up some of my code in this area as well, to make the add order clearer.

@tig Could you point me to an example for how to implement scrolling based on the OP above? Am I correct that the appropriate place to do that is AdvanceFocus()? I am trying to determine if the control that is about to get focus is in the Viewable area of the screen and if not, move the viewable area so that it is visible.

Basically, as I am tabbing through the controls on a View I want to make sure that the one that is about to get focus is in the viewable area of the parent View, and if not, "scroll" the view so that the control about to get focus is viewable.

@tig
Copy link
Collaborator

tig commented Nov 26, 2024

@tig Could you point me to an example for how to implement scrolling based on the OP above? Am I correct that the appropriate place to do that is AdvanceFocus()? I am trying to determine if the control that is about to get focus is in the Viewable area of the screen and if not, move the viewable area so that it is visible.

Basically, as I am tabbing through the controls on a View I want to make sure that the one that is about to get focus is in the viewable area of the parent View, and if not, "scroll" the view so that the control about to get focus is viewable.

I've been noodling on this... Would you be willing to submit a PR that does this:

  1. Add a ViewportSettings.ScrollToMakeFocusedViewVisible flag - this behavior will only be enabled if a dev sets this flag on the View that is hosting the subviews that might need to be scrolled into view.
  2. Add a View.ScrollToMakeSubViewVisible(View subview) API (in View.Content.cs).
  3. On line 594 of View.Navigation.cs, in SetHasFocusTrue() add:
if (SuperView?.ViewportSettings.HasFlag(ViewportSettings.ScrollToMakeFocusedViewVisible) 
{
  SuperView?.ScrollToMakeSubViewVisible(this);
}

The ScrollToMakeSubViewVisible(View view) API would:

  • Determine how far in the x and y direction the Viewport would need to be scrolled to make view fully visible. It would call ScrollHorizontal()/ScrollVertical() with these values.

I think this should work pretty well!

We can then work on edge-cases like if the subview in question is too big to be scrolled fully into view.

@dasien
Copy link
Contributor Author

dasien commented Nov 26, 2024

Yes i will work on that, sounds like fun and the solution makes sense. It's Thanksgiving week and i have all my kids in town, so it may be a slow week for hobby coding :)

@dasien
Copy link
Contributor Author

dasien commented Dec 4, 2024

@tig I have the code staged to commit, but i wanted to make sure i put some kind of tests in. Is SetFocusTests.cs the appropriate place? All i can really test from a unit test perspective is that after the move, the ViewPort and parent View.Frame are aligned and the SubView is visible.

@tig
Copy link
Collaborator

tig commented Dec 5, 2024

@tig I have the code staged to commit, but i wanted to make sure i put some kind of tests in. Is SetFocusTests.cs the appropriate place? All i can really test from a unit test perspective is that after the move, the ViewPort and parent View.Frame are aligned and the SubView is visible.

Yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Issues regarding Terminal.Gui design (bugs, guidelines, debates, etc...)
Projects
Status: No status
Development

No branches or pull requests

3 participants