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

Add mouse/keyboard resizing of views #2537

Closed
2 of 4 tasks
Tracked by #2994
tig opened this issue Apr 13, 2023 · 19 comments · Fixed by #3751
Closed
2 of 4 tasks
Tracked by #2994

Add mouse/keyboard resizing of views #2537

tig opened this issue Apr 13, 2023 · 19 comments · Fixed by #3751
Assignees
Labels
enhancement v2 For discussions, issues, etc... relavant for v2
Milestone

Comments

@tig
Copy link
Collaborator

tig commented Apr 13, 2023

Right now, dragging is handled w/in TopLevel. This needs to change to be handled within Border (or in Application, but via an interface with Border).

  • Define ViewArrangement.Movable - if set and view has a top Border, it can be dragged with mouse. Always with keyboard (although no visual indicator if no border).
  • Define ViewArrangement.Sizable - if set and a view has left/right/bottom Border it can be resized with mouse. Always with keyboard (although no visual indicator if no border).
  • Determine keyboard drag/size UI
  • Remove temporary hack in Application.cs:
image

Related:

My proposal:

Tiled Mode:

Mouse:

  • Dragging Border top or bottom resizes the view vertically
  • Dragging Border left or right resizes the view horizontally

Keyboard:

  • Hit Ctrl+F5 to enable Arrange Mode 1
  • A line will get a indicating it's active for resizing. Left/Right/Up/Down will size.
  • Tab to move to next line
  • Shift-Tab to move back
  • Ctrl+F5 or Esc to exit Arrange Mode

Overlapped mode:

Mouse:

  • Dragging Border top moves the view
  • Dragging Border bottom resizes the view vertically
  • Dragging Border left or right resizes the view horizontally

Keyboard:

  • Hit Ctrl+F5 to enable Arrange Mode 1
  • A appears in top left corner of Border indicating Arrange Mode
  • If ViewArrangement.Movable is set Left/Right/Up/Down will move the view
  • Tab and Shift+Tab will cycle through any sides that can be sized and "Move"
    • UNLESS: ViewArrangement.Resizable (all side-flags) is set. Then appears in bottom right corner of Border.
  • A side selected for sizing will get centered indicating it's active for resizing.
  • Left/Right/Up/Down will size as appropriate
  • Ctrl+F5 to exit Arrange Mode

Footnotes

  1. On Windows Alt-Space, M enables move-with-keyboard. On Mac Ctrl-F5 "Move focus to the window toolbar". No standard on Linux. Ctrl-F5 isn't used by Windows Terminal. Happy for a better suggestion. 2

@tig
Copy link
Collaborator Author

tig commented Apr 16, 2023

This needs to fix #2536 too

@tig
Copy link
Collaborator Author

tig commented Apr 17, 2023

Based on @Nutzzz's excellent SpinnerView enhancments, I propose we use SpinnerView to show which border has focus for sizing instead of the diamond that @tznind originally used for TileView.

E.g. This one for horizontal:
HNlkyoS 1

and this one for vertical:
TubYa08 1

This will require LineCanvas to support hosting a SpinnerView as a point (where .Length == 0) I think.

@Nutzzz
Copy link
Contributor

Nutzzz commented Apr 18, 2023

Y'know, I like the idea in general, but I'm personally not thrilled about those spinners in particular. I feel like one of these might be better (if not perfect).

[What do you think, 2-frame or 3-frame?]

2 frames, horizontal and vertical:
Recording 2023-04-18 112921
3 frames, horizontal and vertical:
Recording 2023-04-17 175433

@tig tig added enhancement v2 For discussions, issues, etc... relavant for v2 labels Apr 19, 2023
@tig
Copy link
Collaborator Author

tig commented Nov 26, 2023

@BDisp this is another Issue related to

@tznind
Copy link
Collaborator

tznind commented Jun 1, 2024

If one of the goals of this feature is to support TGD resizing use cases then I think that the solution needs to be decoupled from Borders.

It must be possible to drag and resize any view (regardless of whether it has a Border). For example:

resize-updated

It also needs to be possible to drag multiple views at once e.g.

drag-multiple

@tznind
Copy link
Collaborator

tznind commented Jun 1, 2024

The relevant code in TGD is in:

MouseManager
DragOperation
DragMemento
ResizeOperation

@tig
Copy link
Collaborator Author

tig commented Jun 1, 2024

If one of the goals of this feature is to support TGD resizing use cases then I think that the solution needs to be decoupled from Borders.

It must be possible to drag and resize any view (regardless of whether it has a Border). For example:

resize-updated resize-updated

It also needs to be possible to drag multiple views at once e.g.

drag-multiple drag-multiple

I'll start thinking about this...

I can't see anything really challenging, but it will require some thought...

Are you interested in collaborating with me on it?

@tznind
Copy link
Collaborator

tznind commented Jun 2, 2024

Are you interested in collaborating with me on it?

Definetly! lets see what we can do.

@tznind
Copy link
Collaborator

tznind commented Jun 2, 2024

Drag

Drag Phases (i.e. State)

In order to support Undo and other stuff (like cancelling an ongoing drag) I think we should keep state in the same way TGD does with DragOperation and it's DragMemento:

  • Only one drag operation can be running at once
  • Use ContinueDrag style

So the process is:

// Establishes the origin from which all vectors are measured
StartDrag (x,y)

// Updates positions of Views, each vector replaces the last (i.e. they are not additive)
ContinueDrag(new Vector(2,5))
ContinueDrag(new Vector(3,5))
ContinueDrag(new Vector(4,5))

// View is moved to x+4 y+5 and drag state is cleared
EndDrag()

Drag Multiple At Once

TGD lets you select multiple views with a selection box. I think this can be left in TGD. But we do need the ability to drag multiple views at once.

For sanity sake probably we should require that all views in a multiple drag should be in the same parent View.

Drag Boundary

When a drag results in some/all of a view spilling out of it's parent:

    /// <summary>
    /// True to allow dragging a view such that part/all of it is
    /// outside of its parent View's viewport
    /// </summary>
    bool AllowDragOffscreen;

Drag Initiation

There are 3 options for a View when it comes to initiating a drag

enum Dragability
{
    /// <summary>
    /// View cannot be dragged
    /// </summary>
    NotDraggable,

    /// <summary>
    /// View can be dragged by clicking and dragging at row 0 of
    /// the view.
    /// </summary>
    DragFromTop,

    /// <summary>
    /// The view can be dragged by clicking and dragging anywhere
    /// in the view
    /// </summary>
    DragFromAnywhere
}

Pos Types

When dragging we need to decide which Pos types can be moved and which cannot.

For example:

  • PosAbsolute (yes)
  • PosPercent (yes but be careful to translate new coordinates to keep as Percent)
  • PosFunc (no)
  • PosView (maybe)

Dragging a view with another that is PosView connected to it (e.g. Left of) needs considered also. Especially if dragging to another view.

Drag into another View

This is an opt in feature. It could be a global value or View specific. But if it is tied to Views then it makes multiple view drags more complicated.

bool AllowDragToOtherViews;

To support dragging a view into another view we must think:

  • Does it make sense to drag a view into a Button (probably not)
  • Does it make sense to drag a view into a FrameView (yes)

So we need some kind of confirmation probably an event with a Cancel member is easiest

event EventHandler<DragEventArgs> StartDrag;
event EventHandler<DragEventArgs> EndDrag;
class DragEventArgs
{
    /// <summary>
    /// Describes the views being moved and where they are being moved to.
    /// Note that you can use set to change which views are dragged e.g. to  drag multiple
    /// </summary>
    public DragOperation DragOperation { get; set; }

    /// <summary>
    /// Set to true to abandon the <see cref="DragOperation"/> and return
    /// all views to original positions
    /// </summary>
    public bool Cancel { get; set; }
}

Composite views

Some views have sub views that complicate click and drag. For example TabView. We need something more structured than the current hacky implementation I have:

/// <summary>
/// <para>
/// Sometimes <see cref="View.FindDeepestView"/> returns what the library considers
/// the clicked View rather than what the user would expect.  For example clicking in
/// the <see cref="Border"/> area of a <see cref="TabView"/>.
/// </para>
/// <para>This works out what the real view is.</para>
/// </summary>
/// <param name="hit"></param>
/// <returns></returns>
public static View? UnpackHitView(this View? hit)
{
    if (hit != null && hit.GetType().Name.Equals("TabRowView"))
    {
        hit = hit.SuperView;
    }

    // Translate clicks in the border as the real View being clicked
    if (hit is Border b)
    {
        hit = b.Parent;

    }

    // TabView nesting of 'fake' views goes:
    // TabView
    //   - TabViewRow
    //   - View (pane)
    //     - Border (note you need Parent not SuperView to find Border parent)

    if (hit?.SuperView is TabView tv)
    {
        hit = tv;
    }

    return hit;
}

@BDisp
Copy link
Collaborator

BDisp commented Jun 2, 2024

A few years ago I was interested in implementing mouse/keyboard drag and drop and resizing. Then I lost the will to continue.
Indeed, @tznind is right in stating that some of the features only make sense in TGD, otherwise any user could create another IDE very easily if TG already provides it. For example, it doesn't make sense that a TextField can be resized or moved in TG, but only in TGD. Drag from FileDialog to a TextView is valid in TG to open a file but in TGD it is moving the FileDialog to the limits of the TextView. Drag from ListView over the selected item to a TextField is valid in TG, and a ContextMenu can be opened to choose to copy or move the item, but in TGD it is moving the ListView to the limits of the TextField, or possibly linking the TextField.Text with the selected item.
In fact, the resizing and movement of a view must be decoupled from the border and only be coupled to the limits of the frame for resizing and within the limits of the frame for movement. In TG, only views that need to be resized and movable, their code must be overwritten in the border. In TGD the behavior will be normal, activating the necessary properties to be able to use them.
In TG, by default, a view cannot be resizable or movable, but only in some derived classes, such as Window and Dialog, if they are configured for that purpose. If it is resizable then a border must exist in order to select the appropriate glyph on either side of its limits. If it is simultaneously movable, in addition to the border, the thickness of the top of the border must be at least two, which will allow the view to be moved if the mouse is placed from the second thickness.
I'm glad this is being debated and will be very relevant for the advancement of this resource.

@tig
Copy link
Collaborator Author

tig commented Jun 2, 2024

Good stuff.

Not a lot of thoughts so far. But one...

Add a flag to Arrangement:

  • DesignMode

When set, effectively hides the Viewport from the mouse hit test code. Thus press and drag anywhere from Border-in works. Even if Border thickness is 0.

@tig
Copy link
Collaborator Author

tig commented Jun 2, 2024

Also, look at Highlight/HighlightMode. Can be extended to be combined with Arrangement.DesignMode to enable the whole view to be highlighted.

@BDisp
Copy link
Collaborator

BDisp commented Jun 2, 2024

But it would be useful if Arrangement.DesignMode was only visible internally, therefore only to TG and TGD.

@tig
Copy link
Collaborator Author

tig commented Jun 2, 2024

But it would be useful if Arrangement.DesignMode was only visible internally, therefore only to TG and TGD.

Why would we not want someone else to build a design-time tool like TGD?

@BDisp
Copy link
Collaborator

BDisp commented Jun 2, 2024

Why would we not want someone else to build a design-time tool like TGD?

I thought you wanted TGD exclusivity that belongs to gui-cs. If that's not the case then I take back what I said.

@dodexahedron
Copy link
Collaborator

It sucks so much for us that every platform and use tha forces us to have to have a "driver" to jump through different hoops to make this all happen. 😤

It's things like this that make libraries like TG so damn nice to use - abstracting away all the BS so the consumer can have a more consistent experience without worrying about it.

/gripe 😆

@tig
Copy link
Collaborator Author

tig commented Sep 10, 2024

In #3705 we now have Keyboard moving and sizing of views per the design above for ViewArrangement.Overlapped.

  • Hit Ctrl+F5 to enable Arrange Mode [^whyctrlf5]
  • A appears in top left corner of Border indicating Arrange Mode
  • If ViewArrangement.Movable is set Left/Right/Up/Down will move the view
  • Tab and Shift+Tab will cycle through any sides that can be sized and "Move"
    • UNLESS: ViewArrangement.Resizable (all side-flags) is set. Then appears in bottom right corner of Border.
  • A side selected for sizing will get centered indicating it's active for resizing.
  • Left/Right/Up/Down will size as appropriate
  • Ctrl+F5 to exit Arrange Mode

nY8gsO7 1

@tig
Copy link
Collaborator Author

tig commented Sep 11, 2024

FWIW, this model supports Views without a Border.

Right now mouse doesn't work when in "Arrange Mode", but it willl....

sWoyDXI 1

@tig
Copy link
Collaborator Author

tig commented Sep 12, 2024

And now....

We have full mouse move/resize capability!

7gi42jA 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement v2 For discussions, issues, etc... relavant for v2
Projects
Status: ✅ Done
Status: 🆕 Not Triaged
5 participants