15 KiB
V2 Spec for View refactor
IMPORTANT: I am critical of the existing codebase below. Do not take any of this personally. It is about the code, not the amazing people who wrote the code.
ALSO IMPORTANT: I've written this to encourage and drive DEBATE. My style is to "Have strong opinions, weakly held." If you read something here you don't understand or don't agree with, SAY SO. Tell me why. Take a stand.
This covers my thinking on how we will refactor View and the classes in the View heirarchy(inclidng Responder). It does not cover
- Text formatting which will be covered in another spec.
- TrueColor support which will be covered separately.
- ConsoleDriver refactor.
Terminal.Gui v2 View-related Lexicon & Taxonomy
- View - The most basic visual element in Terminal.Gui. Implemented in the
Viewbase-class. - SubView - A View that is contained in antoher view and will be rendered as part of the containing view's ContentArea. SubViews are added to another view via the
View.Addmethod. A View may only be a SubView of a single View. - SuperView - The View that is a container for SubViews. Referrs to the View another View was was added to as SubView.
- Child View - A view that is held by another view in a parent/child relationshiop, but is NOT a SubView. Examples of this are sub-menus of
MenuBar. - Parent View - A view that holds a reference to another view in a parent/child relationship, but is NOT a SuperView of the child.
- Thickness - Describes how thick a rectangle is on each of the rectangle's four sides. Valid thickness values are >= 0.
- Margin - Means the Thickness that separtes a View from other SubViews of the same SUperView.
- Title - Means text that is displayed for the View that describes the View to users. For most Views the Title is displayed at the top-left, overlaying the Border.
- Border - Means the Thickness where a visual border (drawn using line-drawing glyphs) and the Title are drawn. The Border expands inward; in other words if
Border.Thickness.Top == 2the border & title will take up the first row and the second row will be filled with spaces. - Adornments - The Thickness between the Margin and Padding. The Adornments property of
Viewis aView-subclass that hosts SubViews that are not part of the View's content and are rendered within the Adornment Thickness. Examples of Adornments:- A
TitleBarrenders the View'sTitleand a horizontal line defining the top of the View. Adds thickness to the top of Adornments. - One or more
LineViews that render the View's border (NOTE: The magic ofLineCanvaslets us automatically have the right joins for these andTitleBar!). - A
Vertical Scrollbaradds thickness toAdornments.Right(or.Leftwhen right-to-left language support is added). - A
Horizontal Scrollbaradds thickness toAdornments.Bottomwhen enabled. - A
MenuBaradds thickness toAdornments.Top(NOTE: This is a change from v1 wheresubview.Y = 1is required). - A
StatusBaradds thickness otAdornments.Bottomand is rendered at the bottom of Padding. - NOTE: The use of
View.Addin v1 to add adornments to Views is the cause of much code complexity. Changing the API such thatView.Addis ONLY for subviews and adding aView.Adornments.AddAPI for menu, statusbar, scroll bar... will enable us to signficantly simplify the codebase.
- A
- Padding - Means the Thickness inside of an element that offsets the
Contentfrom the Border. (NOTE: in v1Paddingis OUTSIDE of theBorder). Padding is{0, 0, 0, 0}by default. - Frame - Means the
Rectthat defines the location and size of theViewincluding all of the margin, border, padding, and content area. The coordinates are relative to the SuperView of the View (or, in the case ofApplication.Top,ConsoleDriver.Row == 0; ConsoleDriver.Col == 0). The Frame's location and size are controlled by eitherAbsoluteorComputedpositioning via the.X,.Y,.Height, and.Widthproperties of the View. - VisibleArea - Means the area inside of the Margin + Border (Title) + Padding.
VisibleArea.Locationis always{0, 0}.VisibleArea.Sizeis theView.Frame.Sizeshrunk by Margin + Border + Padding. - ContentArea - The
Rectthat describes the location and size of the View's content, relative toVisibleRect. IfContentArea.Locationis negative, anything drawn there will be clipped and any subview positioned in the negative area will cause (optional) scrollbars to appear (making the Thickness of Padding thicker on the appropriate sides). IfContentArea.Sizeis changed such that the dimensions fall outside ofFrame.Size shrunk by Margin + Border + Padding, drawning will be clipped and (optional) scrollbars will appear. - Bounds - Synomous with VisibleArea. (Debate: Do we rename
BoundstoVisbleAreain v2?) - ClipArea - Means the currently vislble portion of the Content. This is defined as a
Rectin coordinates relative to ContentArea (NOT VisibleArea) (e.g.ClipArea {X = 0, Y = 0} == ContentArea {X = 0, Y = 0}). ThisRectis passed toView.Redraw(and should be named "clipArea" not "bounds"). It defines the clip-region the caller desires theRedrawimplementation to clip itself to (see notes on clipping below). - Modal - The term used when describing a View that was created using the
Application.Run(view)orApplication.Run<T>APIs. When a View is running as a modal, user input is restricted to just that View untilApplication.Runexits. - TopLevel - The term used to describe a view that is both Modal and can have a MenuBar and/or StatusBar.
- Window - A View that is
- Tile, Tiled, Tiling - Refers to a form of layout where the SubViews of a View are visually arranged such that their Frames abut each other and do not overlap. In a Tiled view arragnement there is no Z-ordering.
- Overlap, Overlapped, Overlapping - Refers to a form of layout where the SubViews of a View are visually arranged such that their Frames can overlap. In Overlap view arragements there is a Z-axis (Z-order) in addition to the X and Y dimension. The Z-order indicates which Views are shown above other views.
Questions
-
@bdisp - Why does
TopLevel.Activate/Deactivateexist? Why is this just notFocus? -
@bdisp - is the "Mdi" concept, really about "non-modal views that have z-order and can overlap"? "Not Mdi" means "non-modal views that have the same-zorder and are tiled"
* `View.MouseEvent` etc... should always use `ContentBounds`-relative coordinates and should constrained by `GetClipRect`. -
After many fits and starts we have clipping working well. But the logic is convoluted and full of spaghetti code. We should simplfiy this by being super clear on how clipping works.
View.Redraw(clipRect)specifies aclipRectthat is outside ofView.GetClipRectit has no impact (only aclipRectwhereView.ClipRect.Union(clipRect)makes the rect smaller does anything).- Changing
Driver.ClipRectfrom within aDrawimplementation to draw outside of theContentBoundsshould be disallowed (see non-ContentBounds drawing below).
-
Border (margin, padding, frame, title, etc...) is confusing and incorrect.
- The only reason FrameView exists is because the original architecture didn't support offsetting
View.Boundssuch that a border could be drawn and the interior content would clip correctly. Thus Miguel (or someone) built FrameView with nestedContentViewthat was atnew Rect(+1, +1, -2, -2).Borderwas added later, but couldn't be retrofitted intoViewsuch that ifView.Border ~= nulljust worked likeFrameView- Thus devs are forced to use the clunky
FrameViewinstead of just settingView.Border.
- It's not possilbe for a
Viewto utilizeBorder.BorderBrush. Borderhas a bunch of confusing concepts that don't match other systems (esp the Web/HTML)Marginon the web means the space between elements -Borderdoesn't have a margin property, but does has the confusingDrawMarginFrameproperty.Borderon the web means the space where a border is drawn. The current implementaiton confuses the termFrameandBorder.BorderThicknessis provided. In the new world, but because of the confusion betweenPaddingandMarginit doesn't work as expectedc.Paddingon the web means the padding inside of an element between theBorderandContent. In the current implementationPaddingis actually OUTSIDE of theBorder. This means it's not possible for a view to offset internally by simply changingBounds.Contenton the web means the area inside of the Margin + Border + Padding.Viewdoes not currently have a concept of this (butFrameViewandWindowdo via thier privateContentViews.Borderhas aTitleproperty. IfViewhad a standardTitleproperty could this be simplified (or should views that implement their own Title property just leverageBorder.Title?).- It is not possilble for a class drived from View to orverride the drawing of the "Border" (frame, title, padding, etc...). Multiple devs have asked to be able to have the border frame to be drawn with a different color than
View.ColorScheme.
- The only reason FrameView exists is because the original architecture didn't support offsetting
-
API should explicitly enable devs to override the drawing of
Borderindependently of theView.Drawmethod. See howWM_NCDRAWworks in wWindows (Draw non-client). It should be easy to do this from within aViewsub-class (e.g. overrideOnDrawBorder) and externally (e.g.DrawBorder += () => .... -
AutoSizemostly works, but only because of heroic special-casing logic all over the place by @bdisp. This should be massively simplified. -
FrameViewis superlufous and should be removed from the heirarchy (instead devs should just be able to manipulateView.Borderto achieve whatFrameViewprovides). The internalFrameView.ContentViewis a bug-farm and un-needed ifView.Borderworked correctly. -
TopLevelis currently built around several concepts that are muddled:- Views that host a Menu and StatusBar. It is not clear why this is and if it's needed as a concept.
- Views that can be run via
Application.Run<TopLevel>. It is not clear why ANY VIEW can't be run this way - Views that can be used as a pop-up (modal) (e.g.
Dialog). As proven byWizard, it is possible to build a View that works well both ways. But it's way too hard to do this today. - Views that can be moved by the user.
- If
Viewclass is REALLY required for enabling one more of the above concepts (e.g.Window) it should be as thin and simple as possilbe (e.g. should inherit fromFrameView(or just useView.Bordereffecively assumingFrameViewis nuked) instead of containing duplicate code).
-
The
MdiContainerstuff is complex, perhaps overly so. It's also mis-named because Terminal.Gui doesn't actually support "documents" nor does it have a full "MDI" system like Windows (did). It seems to represent features useful in overlapping Views, but it is super confusing on how this works, and the naming doesn't help. This all can be refactored to support specific scenarios and thus be simplified. -
There is no facility for users' resizing of Views. @tznind's awesome work on
LineCanvasandTileViewcombined with @tig's experiments show it could be done in a great way for both modal (overlapping) and tiled Views. -
DrawFrameandDrawTitleare implemented inConsoleDriverand can be replaced by a combination ofLineCanvasandBorder. -
Colors -
- As noted above each of Margin, Border, Padding, and Content should support independent colors.
- Many View sub-classes bastardize the exiting ColorSchemes to get look/feel that works (e.g.
TextViewandWizard). Separately we should revamp ColorSchemes to enable more scenarios.
-
Responderis supposed to be where all common, non-visual-related, code goes. We should ensure this is the case. -
Viewshould have default support for scroll bars. e.g. assume in the new worldView.ContentBoundsis the clip area (defined byVIew.FrameminusMargin+Border+Padding) then if any view is added withView.Addthat has Frame coordinates outside ofContentBoundsthe appropriate scroll bars show up automatgically (optioally of course). Without any code, scrolling just works. -
We have many requests to support non-full-screen apps. We need to ensure the
Viewclass heirachy suppports this in a simple, understandable way. In a world with non-full-screen (where screen is defined as the visible terminal view) apps, the idea thatFrameis "screen relative" is broken. Although we COULD just define "screen" as "the area that bounds the Terminal.GUI app.".- Question related to this: If
View.Borderworks correctly (margin, border, padding, content) and if non-full-screen apps are supported, what happens if the margin ofApplication.Topis not zero (e.g.Border.Margin = new Thickness(1,1)). It feels more pure that such a margin would make the top-left corner ofApplication.Top's border be attConsoleDriver.Row = 1, Column = 1). If this is thw path, then "screen" meansApplication.Top.Frame). This is my preference.
- Question related to this: If
Thoughts on Built-in Views
LineViewcan be replaced byLineCanvas?ButtonandLabelcan be merged.StatusBarandMenucould be combined. If not, then at least made more consistent (e.g. in how hotkeys are specified).
Design
Responder("Responder base class implemented by objects that want to participate on keyboard and mouse input.") remains mostly unchanged, with minor changes:- Methods that take
Viewparametsrs (e.g.OnEnter) change to takeResponder(bad OO design). - Nuke
IsOverriden(bad OO design) - Move
View.DatatoResponder(primitive) - Move
CommandandKeyBindingstuff fromView. - Move generic mouse and keyboard stuff from
View(e.g.WantMousePositionReports)
- Methods that take
Example of creating Adornments
// ends up looking just like the v1 default Window with a menu & status bar
// and a vertical scrollbar. In v2 the Window class would do all of this automatically.
var top = new TitleBar() {
X = 0, Y = 0,
Width = Dim.Fill(),
Height = 1
LineStyle = LineStyle.Single
};
var left = new LineView() {
X = 0, Y = 0,
Width = 1,
Height = Dim.Fill(),
LineStyle = LineStyle.Single
};
var right = new LineView() {
X = Pos.AnchorEnd(), Y = 0,
Width = 1,
Height = Dim.Fill(),
LineStyle = LineStyle.Single
};
var bottom = new LineView() {
X = 0, Y = Pos.AnchorEnd(),
Width = Dim.Fill(),
Height = 1,
LineStyle = LineStyle.Single
};
var menu = new MenuBar() {
X = Pos.Right(left), Y = Pos.Bottom(top)
};
var status = new StatusBar () {
X = Pos.Right(left), Y = Pos.Top(bottom)
};
var vscroll = new ScrollBarView () {
X = Pos.Left(right),
Y = Dim.Fill(2) // for menu & status bar
};
Adornments.Add(titleBar);
Adornments.Add(left);
Adornments.Add(right);
Adornments.Add(bottom);
Adornments.Add(vscroll);
var treeView = new TreeView () {
X = 0, Y = 0, Width = Dim.Fill(), Height = Dim.Fill()
};
Add (treeView);