SmartTable.h
The widget itself. Every node, every panel field, and the C++ behind them.
A data table for UMG.
Columns are named once, in the Details panel or through SetColumns. A column binds by name to a property, or to a function that takes no input. Text, sorting and filtering all read that one binding. The header and the rows cannot end up reading different things.
Table->SetItems( Ships );
FSmartTableColumn Shield; // a column that draws its own widget Shield.ColumnId = "Shield"; Shield.BindingName = "ShieldPercent"; Shield.CellClass = UMyBarCell::StaticClass();
Most columns are one line and no code at all. A column with no CellClass gets the built-in text cell.
Rows arrive two ways. SetItems takes objects and builds a model inside the table. Everything else comes in through SetModel: a file, a server, a million rows.
The table itself never sorts. It says a sort was asked for and then draws whatever order the model hands back.
- Class
- USmartTable
- Extends
- UWidget
- Members
- 399
- Blueprint nodes
- 99
- Panel fields
- 68
Constructor 1
Public functions 157
Smart Table 28
Replaces the whole set. It is fine before the widget is built, since a table on a tab not opened yet is the normal case.
Items in both sets keep their rows. The new ones arrive the way AddItem rows do, with the entrance and the scroll. A set that shares no item with the last one is a new set, and nothing in it arrives.
Adds at the end. The row plays the entrance, and bStickToEnd with bScrollToAddedRow say where the view goes for it.
Takes the row out in the same call, while the table draws the items SetItems gave it. With any other model in force it takes nothing out and logs why. A row on its way out is not counted and not selected. That holds while the model sorts on the game thread. With a WorkDispatcher set, a sort already in flight can still count the row until it lands.
Empties the built-in items model and makes it the one in force. A model handed to SetModel keeps everything it holds. It stops being the model this table draws, and nothing more.
Takes a DataTable. Columns bind to the row struct fields by the same names, and nothing is copied. Rows get read where they sit.
This item changed. Its row reads again. No other row does, and the order stays.
True when the view sat at the end of the list, as of the last tick.
Within a quarter of a row of the bottom counts as the end. Exactly zero would stop a table following over a drag that ended a pixel short.
Sampled once a tick, not read when asked. By the time a row is added the list has already grown, and a fresh read always says no.
if ( Table->IsScrolledToEnd() ) Table->ScrollRowIntoView( Row );
Every visible row reads again. This is the one to call when a lot of items changed at once.
The item on the first selected row, or null when the model builds none. A DataTable model does build one, so this answers over a DataTable too.
One entry per selected row that has an item. A DataTable model has one for every row: it builds a row proxy on request.
The selection as rows. The only form a model with no items can use.
Natural row, so it names the same row after any sort. False until the widget is built.
Does nothing for a row a filter is hiding: only a drawn row can be selected.
Select or clear a set of rows at once, and broadcast once for the lot.
A loop over SetRowSelected broadcasts per row, and every listener reads the whole selection back out each time. Rows a filter is hiding are dropped, and the log says how many.
The same for items, through whatever the model answers for NaturalRowOfItem.
An item this model does not hold is dropped. A filter hiding the row drops the item too.
Finds the drawn row holding that item, and does nothing when a filter is hiding it.
Drops every selected row. Safe before the widget is built, where it does nothing.
Scrolls and never selects. Does nothing when a filter is hiding the row that holds the item.
Scrolls to a row by number, for a model that has no items to scroll to.
ScrollItemIntoView needs an object, and a struct model has none.
Scrolls to the last row the table is showing.
The end in presented space, and not the model's last row. Under a filter that row might not be drawn at all. The end here means the bottom of what is on screen.
bStartAtEnd is the same move at the moment the table is built.
A cell with more than one line of text needs tall rows. The default is one line.
With the header dropped, the same columns draw as a plain list of cells.
A runtime setting and not only a Details panel flag, so a table can drop its header and take it back while it is running.
Whether a click can reach the widget inside a cell. Rebuilds every row.
With it on, a control in one column works, and every other column still selects the row under it. It is not one or the other.
Whether a click reaches inside a cell. Off, the row under it takes every click.
Whether the table takes the height it is given or asks for the height of its rows.
The runtime half of HeightMode, and the pair to SetShowHeader. A panel built in code that drops the header usually stops being a scrolling list too.
MaxVisibleRows is skipped unless the mode is FitRows.
Whether clicking a row selects it, and how many rows can be selected.
None is for a table whose job is editing, such as a grid over a DataTable where the row marking only gets in the way of the field being changed.
It pairs with SetInteractiveCells( true ). That call lets the click reach the widget in the cell. This one stops the row lighting up behind it.
How many rows can be picked at once.
Raises OnCellValueChanged. A cell calls this when a control inside it moved.
Reflected because a cell is a Widget Blueprint. Left as C++ only, it would give a cell graph no way to raise OnCellValueChanged at all.
Input 16
Gives the row list keyboard focus.
SetFocus goes to the widget root, which then passes the keyboard down. This skips the root and goes straight to the list. The engine's own key handling then covers the arrows, the D-pad and the stick.
The row the caret is on. Enter runs it, and MoveSelection moves from it.
With one row selected the caret is on it. With several selected the caret is on one of them, and that is the row Enter runs. INDEX_NONE when there is no caret at all.
Moves the caret by Delta rows in presented order, and selects the row it reaches. With no caret yet, a move forward starts at the first row and a move back at the last.
The same, by whole screens. A page is as many rows as the list really built. That tracks the real size of the table instead of a number this plugin picked.
The first row as drawn, so a sort or a filter changes which row that is.
The last row as drawn, which a sort or a filter also changes.
Raises OnRowActivated for the caret row, with no mouse. The pointer route is a click twice.
One step of the sort cycle on a column: ascending, descending, then back to unsorted if the table lets it. What a header click does, and it saves the layout the same way.
Not SortByColumn, which sets an order at once and never saves.
Opens the built-in header menu on a column, under its header.
HeaderMenuOptions.bEnabled gates this call and not only the right click. Every other gate in the plugin takes the gesture and leaves the function.
Each entry the menu offers has a function of its own, so an author with the menu off reaches all of them from a graph.
Closes whichever menu this table has open, if any.
Nothing has to call this. A menu does not go when the table does. A watch on the table's own widget shuts the menu two frames after it leaves the screen.
This is for shutting one on purpose, or for a screen that cannot wait those two frames.
The menu with no column under it, which is the one that brings hidden columns back.
Starts a column resize that a held control drives.
Begin, Update and End instead of one call with a width, because a resize keeps going: a stick held against an edge moves the column the way a dragged mouse does.
A pointer press on a grip takes over a resize started this way, and its release ends the whole thing.
Widens or shrinks the column being resized by DeltaPixels.
Pixels, not stick units. Turning a stick push into pixels sits with the input settings, not here.
Ends it, and saves the layout once for the whole drag instead of once per step.
Off leaves a right click a plain click, which is what opens the row menu.
Whether this table, or any row or cell inside it, holds the keyboard.
Columns 11
Selects one column. Every intent that gets no column id is then aimed at it.
Moving the aim and using it are separate calls, so one button can walk the selection while another acts on it.
The table also selects on its own whenever anything touches a column: its header, a cell in it, or a widget inside that cell.
Walks the aim Delta columns along the shown order. With nothing aimed at yet, a move forward starts at the first shown column and a move back at the last.
NAME_None until something selects one.
Replaces the whole set and rebuilds the header at once. Which columns a second level can order is only marked stale here, and worked out again the next time something asks.
The columns as named, for a model that needs to know what it will be asked for.
The built-in models are handed them by SetColumns. A model written in C++ arrives through SetModel and is never reached into, so one that binds by column has to come and ask.
It is also the only way to read columns named in the Details panel instead of set from code.
Written as a layout override, so ResetLayout puts back what the column was authored with.
Whether it is drawn now: the authored answer with any layout override over the top.
Gives the last shown column whatever width is left over, or stops giving it. The width pass runs at once, so a cell class that sizes itself from its column measures again.
Off also puts the columns back to offset zero, so nothing is left scrolled out of sight.
The column header text as named, or its id when there is none.
Scrolls the columns sideways by a number of pixels. Negative goes left.
The mouse reaches the same thing through the scrollbar. This is the route a pad has to a far column.
Sorting 7
Asks the model for this order. The header arrows follow what the model says, not this call.
It replaces the sort instead of adding to it, and None clears it.
See SmartTable::Sorting::SpecWithPrimary, the same rule as a value, for code that reads it.
Keeps the current primary sort and orders its ties by this column. Shift clicking a header does this.
Two levels at most. On an unsorted table the column becomes the primary, and None takes the level off.
It refuses a level that would move no row. A second arrow over an order that did not change looks like a broken table. bAllowDeadSecondarySort turns the refusal off.
See SmartTable::Sorting::SpecWithSecondary, which has the rules in full.
Asks the model, which can refuse. Then the header arrow goes back and no row moves.
The order the model is holding, not the last one asked for. Empty when there is no model.
Takes effect on the next sort. Nothing is worked out inside this call.
Brings IsDeadSecondaryColumn up to date with the rows as they stand right now.
It sits outside the getter because that getter is const and BlueprintPure, and a pure node can run any number of times in any order. An answer built on first read would show its own call count.
One pass over the presented rows, and only when something marked the answer stale. That gate marks this the DRAWING entry: the Shift held dimming and the greyed menu entry, both of which can be a beat behind. The one site that ACTS on the answer works it out again instead.
True when a second sort level on this column could reorder nothing, because the primary sort left it no ties to break. Always false while bAllowDeadSecondarySort is on.
It reads the last answer RefreshDeadSecondaryColumns worked out, and works out nothing itself.
Filtering 2
Searches every column, hidden ones too, so hiding a column never changes what the filter finds.
What the model is filtering by now. Empty when there is no model.
Model 2
Borrowed, never owned. Closing a table must not take the data with it. Replaces the built-in one.
Whatever the table reads now: one given to SetModel, or the built-in one SetItems fills.
Row Numbers 1
Rebuilds the header and every row, so this is not free to turn on and off per frame.
Drag and Drop 10
Lets cells start drags and take drops. It does nothing else.
Off by default. A cell that can be dragged has to hit test itself, so every cell in the table takes the pointer before the row under it does.
Whether a drag may start from this pointer. True only while the pointer holds a cursor.
An FSlateUser standing for a world-space pointer holds no cursor, and IsVirtualUser is written as that test. NotifyPointerMoveComplete asserts on the cursor inside its drag branch, so a drag from such a pointer trips an engine assert for as long as it lasts.
The assert guards the shape of the cursor and nothing else, so a refusal costs the gesture and nothing more. A cell that starts a drag of its own asks this too.
Whether a cell may start a drag. Off, no cell ever takes the pointer from the row under it.
What this cell is to the drag going on now, or None when there is not one.
Whether a cell drag is out at all. The marking is an attribute on every visible cell and asks this first, so a frame with no drag in it pays one test per cell instead of a role lookup.
It keeps nothing of its own. The two drag rows are what it reads.
Moves rows in front of another one. Every reorder route ends here, the row drag included.
InsertBeforeNaturalRow is a GAP: 0 is in front of every row, GetNumRows is past the last.
It clears the sort once the rows move. A live sort would otherwise put the row straight back.
False means the model refused, and then nothing moved and the sort is still in force.
Moves the selection that many rows. Delta counts PRESENTED rows, which is what the eye counts: one up means the row drawn above, filtered or not.
It stops at the ends and never wraps. False when the selection is already there, and false when a filter is hiding part of it.
Whether rows may be dragged into a new order. Off, a press on a row only ever selects.
Rebuilds every row, because a row reads this once when it is built and never again.
The natural gap that a gap between two DRAWN rows stands for. This is what MoveRowsTo wants.
The rule: a drop lands right after the drawn row above the gap. With a filter on, that row can move far in natural space while on screen it looks like one step.
Style 2
Uses a style asset and rebuilds what draws from it.
The style asset in force, or null while the table draws its own authored look.
Layout 15
What was changed in the table: widths, hidden columns, sort. A width or a hidden flag left at its default stays out. The drawn order is kept apart from those, and is empty until something moves.
Restores a stored layout. It does not raise OnLayoutChanged, since nothing in the table changed it.
Back to the named columns: widths, visibility and sort as the Details panel left them.
Which table this is, so a layout can be kept for it.
Two tables with one id share a layout, which is almost never right. Setting this reads the store again, so one widget used for several sets of data keeps its own widths for each.
Reads the stored layout back at once when TableId is already set, which is the usual order: the id is authored on the widget and the store arrives at runtime.
Sets a column width in pixels and marks it as chosen, so no later resize takes it back.
Forgets a chosen width and gives the column back to its named sizing. A Fill column stretches with the window again, and a Fixed one goes back to its named pixels.
Shrinking a column does not rewrite its OWN chosen width. Widening one can rewrite a NEIGHBOUR'S, on a table with no sideways scroll, where the room has to come from somewhere. ResetLayout drops every chosen width at once.
The column width right now, stretch and all. Zero usually means the width is not known in pixels yet, as for a Fill column before the first layout. It is also the answer for an unknown ColumnId, and for a Fixed column whose author typed zero.
Widens a column to the widest text in it, header and all, capped at MaxAutoSizeColumnWidth. The width counts as chosen afterwards, so no later fit pass takes it back.
Left to right order. A column left out keeps its named place, after the ones named here.
SHeaderRow has no drag of its own, so the header cell carries one. Move Left and Move Right in the header menu drive this call, and so do MoveColumn and MoveColumnNextTo from a graph.
The order as drawn: the authored order with any stored one folded into it. An id naming no column is dropped, and a column no id names keeps the place it was authored in.
Moves a column by Delta places. It is clamped, so a column at either end stays there.
Puts Moved right before or right after Target. This is what dropping a dragged header does.
Whether a right click on the header opens the built-in menu at all.
Whether a column may be DRAGGED by its header. The menu's Move Left and Move Right do not ask this one.
Busy 2
For a model that owns its own work: a sort on a server, a query, a load.
The built-in background path drives this itself. What is left is work the table can not see. Nothing draws until BusyOverlayDelaySeconds passes, so setting and clearing it in one call shows nothing.
Whether the table is waiting on a sort. The overlay only shows after BusyOverlayDelaySeconds.
Rows 3
Opens the row menu on one row, the way a right click there would.
Reflected, so a pad button or a key binding opens it with no right click in the path. RowMenuOptions.bEnabled gates the right click and never this call.
Replaces the entries the next row menu draws.
It is for calling from OnRowMenuOpening, which is why it takes effect at once and not on the next open. The menu gets built from this list the moment that event returns.
Whether a right click on a row opens its menu. RowMenuOptions.bEnabled is where it is set.
C++ only 58
Every item this table holds, in the order the model keeps them.
A view, not a copy. It points straight at the model's own array, so a big table does not copy its items just to be read.
The catch is what ends it. AddItem, RemoveItem, SetItems and ClearItems all move that array, and a view held across any of them points at free memory.
Empty unless the built-in items model is the one in force. A DataTable model keeps no array to point at, though its rows still have items: it builds a row proxy the first time one is asked for.
The Slate "user" this table's open menu was pushed for. A menu no pointer opened belongs to the pointer over the table, or to the keyboard "user" when there is none. A flyout inside it is opened for the same one, or it dies when the focus of that "user" moves.
Ends a pointer resize this exact "user" and pointer began, and says whether there was one.
Not a UFUNCTION: the header row calls it off a release and off a lost capture, and neither is a thing a graph can express. An intent hold is left alone. EndColumnResize is what ends one of those.
Both halves of the pair are matched, so a release from one "user" cannot end a drag another began. A table on a world-space screen is driven by a virtual pointer while the real cursor still exists.
Selects the column that holds LocalX, measured in the header's own space.
This is how a click on a cell selects its column. The row asks for it from a preview press, so the answer is the same whether the click hit the cell drawing or a slider inside it.
The same, without the copy. For C++ only, and empty while this table has no model.
The brush that marks a role. Null for None, and for a role with no brush set.
GetCellDragRole for a row named by its id, which a built row already holds. It asks the model for nothing, and the marking reads it for every visible cell on every draw of a drag.
A cell calls these as a drag starts on it, comes over it, or leaves it. Only a cell does. They are the cell half of the work, public only because a cell is its own class.
Runs when the drag ends, no matter how it ended.
The names SmartTablesEditor needs to reach these properties.
Here and not as text over there, because GET_MEMBER_NAME_CHECKED can not see a protected member from outside the class. A plain TEXT( "Columns" ) in another module is a rename waiting to fail with no log line at runtime, instead of failing loudly at build time.
What this table's columns would be after copying from the named source. Named ones stay, missing ones are added, and nothing is dropped.
It gives an answer back instead of writing one. The Details panel of a UWidget edits a preview copy, and the engine only moves a notified property edit onto the template the asset saves. A function writing straight to Columns reaches the one object thrown out on the next compile. SmartTablesEditor writes this through an IPropertyHandle, which is what the engine listens to.
Names the sources a move to NewSource would clear. Empty text when there is nothing to lose.
Whether the right edge of this column is a resize grip at all.
The one place that answers it. It reads the table's bAllowColumnResize, the column's own bResizable, and whether the column is shown at all. A locked column draws no resize cursor, and its click sorts.
ColumnLayout::FindEdgeAt skips a locked column inside its own walk. Not a second copy: it is a pure function over column sizes with no table in reach, so it can not ask.
True between the start of a resize and the release that ends it, whether a press on a column edge began it or a BeginColumnResize call did.
The same menu, put where a pointer pressed instead of under the row, for the Slate "user" that pressed.
OpenRowMenu sits under the row, which is what a gamepad or a graph needs. It is a UFUNCTION and this is not: a graph has no "user" to name, so it opens a menu for the keyboard one. A pointer does, and on a table drawn off screen naming the wrong one loses the menu the moment the player's real click reaches the game.
Whether a right click opens a menu anywhere on this table: the header menu on a shown header, or the menu of a row.
Whether a right drag is scrolling this table either way. A row asks so it can tell a pan that just ended from a click. Asked of the table and not of the list, because the list is private.
Whether the columns can pan at all. The list asks before it starts counting a sideways right drag, since a drag that can scroll nothing must not end up hiding the row menu.
Takes a cell from the pool, bound to this row and column.
This one and the ones after it are public because Slate widgets in this plugin call them across a class line, which is not the same as being part of the API.
Null when the column id is not one of this table's columns, and null when there is no model yet.
A model with no items is fine: GetItem() gives back null and a cell that reads the model text still works. A CellClass over such a model says so once per column, at Verbose. One with an ItemSetter draws nothing without the item, so it says it as a Warning, outside the designer.
True the first time a cell of CellClass reports an ItemSetter mistake to this table, and false after, so a pool of broken cells says it once.
Takes a row wrapper from the pool, with Columns already in its named slot.
Null in three cases: no RowWidgetClass, a class the pool can not build, and a class with no named slot. The first is the default and says nothing. The last still draws the row, only unwrapped.
The two failures warn once per class, not once per table.
Raises OnCellDropped. The cell that was dropped on calls it, so the target is its own row.
The style members taken from the style class defaults, which is the end of the constructor. Public because rebuilding those defaults after a palette edit has to run exactly this. That sweep is not a table's own behaviour, so it lives in SmartTableStyleRefresh.
Runs again after a palette edit moved something under this table.
Refreshed holds every object the sweep really changed. Only a table can say whether any of them matters to it, because StyleAsset and the live widget are both out of reach from outside. That is why the check sits here and not in the sweep, and why a table nothing moved goes no further.
SmartTable::StyleRefresh::FromSettings calls it, and nothing else does.
The widget for the pinned number column. The row draws it instead of a cell, because it belongs to the table and not to the data, and no model should have to know it is there.
A left click on a header. It sorts. Shift held adds a second level, but only while bAllowSecondarySort is on. With it off, Shift does what a plain click does.
A row drag landing on this table. It moves the rows through MoveRowsTo, and saves the layout when that took a sort off, the way a header click saves. MoveRowsTo called from a graph saves nothing.
Rows is what the drag picked up, found again as it lands. A row that left while the drag was out stays out of the move. InsertBeforeNaturalRow counts the rows as they stand at the drop.
The right click menu of a column, built new each time so it names what is sorted and hidden now.
The same, for a caller that already has the widget path and must not have one built for it.
A header cell pushes from a mouse event and has to give that event its own path. A menu pushed on a path that finds no owning window opens fine, and then can not push a second menu inside it. The sort entries work, and the Columns flyout does nothing at all.
AbsolutePosition is in the WIDGET TREE's absolute space: what a pointer event carries, and what LocalToAbsolute answers. The menu stack reads a different space in a window drawn by an FWidgetRenderer, and this is the one place the two are put back together.
The same at a named screen point, building the path from the anchor.
Every menu this table opens goes through this or PushMenuOnPath, which is why closing the last one and giving focus back are properties of the table. Opening one closes every menu Slate is holding open and not only this table's, since that is what the menu stack offers.
Public because a graph opens menus too, and a menu the table does not know about is one it can not close before freeing the style set it draws from.
The header's own background and label style. The widget a column drag draws under the pointer reads it: that widget is a floating copy of a header with no other way to look like one. A drag outlives the widget that started it, so it can not hold the table.
The arrow for the current sort on this column, or null when nothing sorts by it. Direction only, and the level is said by how many arrows the header draws.
A brush the style set to a Draw As of None comes back like any other. This answers what the sort is, and drawing it is the header's job. Null would say nothing sorts by the column, which is not true.
The colour both sort arrows draw in, which is the colour of the header labels.
Read from the label style and not from the header row, which carries a colour of its own. Two values for one idea drift apart.
True when this column is the second sort level, which the header draws as a second arrow.
The style the nothing to show line draws in.
How tall the header draws. Zero until a style reaches this table.
Which of the two empty messages a table like this needs.
It reads only its arguments, so the choice needs no built table. No rows at all and a filter that matched none look like the same blank box and mean two different things.
Protected functions 9
Fails the Widget Blueprint compile when a column names something that is not there.
A binding is a name, resolved against a struct nothing compiles against. Rename a field and every column that named it goes blank. The first sign is a table of empty cells in a running game: the C++ still builds, the asset still loads, and the column is still well formed.
An error stops the compile, which is the point. A warning would scroll past.
Fills the binding dropdown on every column from ExpectedItemClass. Reflected because the Details panel calls it by name, and it is not part of the script API.
ColumnSource, ExpectedItemClass and SourceTable, put to SmartTable::ResolveColumnSchema. SourceFile is left out of it, since the column builder reads that file straight.
An entry of the row menu was picked. The row is found again and OnRowMenuEntryChosen names it, with the item it holds now.
A menu stays open over frames, and a live feed adds and drops rows while it does. A row that left while the menu was open says nothing.
Private functions 85
Unset in Fill, and unset in FitRows while RowHeight is zero or less. Either one lets the parent height straight through.
One builder for every menu this table opens, over the style resolved for this table.
Show Columns and Reset Columns, which both menus offer and neither owns.
Sets one column cell again in every row on screen, which is what gets a wrapping cell to work out its line breaks again.
No cell this plugin ships wraps. The stock text cell is a bare STextBlock with no wrap set. This is here for a cell class of its own that does.
Says to every cell on screen what it now is to the drag, so a cell that draws its own marking hears about it. The built-in marking needs none of this, being an attribute the row reads every frame.
Everything this table does once per frame, in one place.
One timer on the root, never one per header cell. Ten columns still means one tick a frame, and a table drawing no header at all goes on ticking.
The width the pinned number column needs, which grows with the row count.
What sits left of the first column without being one: the row number gutter, when it is shown. Every walk across the columns starts here. A walk that skips it names the wrong column.
How much width the columns really have to share, less the pinned number column.
The scroll box is what it reads, never the header row. A header inside a scroll box sits at whatever width its own columns asked for. Measure a stretched column against that and it can only grow.
The scroll box takes its size from its parent instead, which is the one number in the sum that does not depend on the answer.
Works out every shown column pixel width again, then reads the size of exactly the cells that moved.
TickTable runs it, and it refuses cheaply: nothing happens unless the inputs are dirty or the free width moved more than half a pixel. Running it twice gives the same answer.
It is also the one place the header is rebuilt for width reasons. Twice is usual and not a ceiling: once when the first pass turns resizable columns Manual, and once when the gutter passes the header.
The gutter cannot be bound at all, since FixedWidth is a Slate argument and not an attribute. It is sized from the row count, so a model still filling in costs one more rebuild per digit.
The next ReconcileColumnWidths runs even when the free width has not moved. For the callers that changed the inputs: a column shown or hidden, a layout restored, a chosen width.
Asks the list to read its row sizes again, and to build any rows the new widths left room for.
Every resize route ends here: the header row grip and the SHeaderRow one.
The width during a drag. Every column is measured again on every move, and the only thing that waits for the mouse coming up is the layout being saved.
Clears what the SHeaderRow sizing grip leaves behind, once a frame from the timer.
That grip settles a width without ever saying a gesture began, so it fills the width snapshot and owes a layout save, and nothing else would ever drop either. Every gesture this class starts ends at its own release instead, so a live one is left alone here.
Ends whatever resize is going on, whichever route opened it. The gesture goes back to rest, and the one layout save the whole drag owes is written.
Both routes come through here, so there is one reset and not two. Reset half of it and the gesture still names a column with no press behind it, which every later read takes for a live pointer drag.
Keeps a live resize going when the cursor leaves the visible part, by scrolling to follow it.
Moves width between DraggedColumnId and the columns to its right, and gives back the widest the dragged column can be.
Fit mode only. There the widths fill the space exactly, so without this a drag that grows a column has nowhere to grow into, and refusing it flat reads as a broken resize.
It does not fit the columns again. Every frame works from the widths the drag started with, so one pointer position always gives the same answer. Fitting again each frame would stack instead, and a pointer held still would go on taking width off the neighbour.
What ColumnId measured when the drag began. It takes the snapshot when there is not one yet.
Late on purpose. The pointer has three ways in and this table owns only two of them. SHeaderRow puts its own sizing grip over the right edge of every manual column and captures the mouse itself. A drag on those few pixels arrives through HandleColumnWidthChanged, which runs neither of the two.
Taking the snapshot on first use covers all three. Every route reads a width before it writes one, so the late snapshot is the press time one.
The column whose right edge is under LocalX, or None. It also says where that column starts.
The Show Columns flyout, as a widget instead of entries poured into a builder the engine built.
FMenuStack draws the panel behind every pushed menu from the Menu.Background of FCoreStyle, hard coded inside FMenuStack. A named background reaches a menu only through a wrapper on top of the content, and that wrapper is SSmartTableMenuHost. A submenu built by AddSubMenu gives nowhere to put one, so its flyout draws in the engine grey right beside a parent using the table style.
The flyout goes through MakeMenuBuilder, so its search box matches the parent.
What the columns are right now, read off Columns, ActiveLayout and ResolvedWidths. It lives for one call. Every read of the columns goes through it.
The stored override for this column, built the first time it is asked for.
The one warning for a ColumnId that names no column, said the same way by every function taking one.
Caller is the function name, so the message still says which call did nothing. The guards stay at the call sites. Half read FindColumn and half read the order array, so one shared check would answer the wrong question for half the callers.
Opens a menu tied to a widget of this table, closing whatever was already up.
The widget path is built here instead of by the caller. It gives a true refusal when the table is off screen, and it is what lets a submenu find its way.
The one route to the list's selection. Every caller gives the whole set, so the list signals once whatever number of rows moved.
Where the caret sits in presented order, or INDEX_NONE when there is none: no caret at all, or a row a filter is hiding right now.
Puts the caret on a presented row, selects it, and scrolls it into view. The one place a MOVE writes both carets together. A rebuild puts FocusedRow back on its own, without going through here.
Whether this row is on screen right now. False for a row a filter took away, and for every row while this table has no model.
Where that row draws, or INDEX_NONE when it does not.
Whether the caret can move at all. It says why not in the log, so every mover refuses the same way.
Enter, and nothing else.
It gives back Unhandled for every other key on purpose, including an Enter with nothing to run. Arrows, the D-pad and the stick only start moving the list once a key event ends unhandled, so a flat Handled() here would kill that with no error anywhere to say why.
Saves through the store when there is one, and says so to listeners. Only for changes in the table.
Fills the table in the designer through the same model route the game uses, so the preview can not differ from what the game draws. With a source set those are the REAL rows, read from the DataTable or the class defaults. Placeholders are what it falls back to with no source.
Throws the row index array away and builds it again from the model presented rows. Nothing is reused: the selected and focused row ids are written down first, and put back afterwards, which is what carries a selection through rows being added or dropped.
Whether the header menu offers "Then Sort By This".
Three things decide it: bAllowSecondarySort, a sort level on some other column, and this column not being a dead secondary. The middle one is what the Shift gesture never asks. With nothing sorted at all, SpecWithSecondary turns the column into the primary. That is right for a click and wrong for an entry starting with Then, which would name a sort that is not there.
Works the dead-secondary answer out again, whatever the dirty flag says.
For the one site that ACTS on it, the refusal inside SortBySecondaryColumn. That site cannot trust the flag. A row tick changes values without re-sorting, and the pass reads live keys over the order in force, so one value moving inside a tie group frees a column with nothing marking it stale.
Refreshing on the flag is right everywhere else, because everywhere else is drawing.
A sort picked from the header menu. It comes through here instead of binding the public sort calls straight, so a menu sort saves the layout exactly as a header click does.
Reads whatever is filed under the id of this table into ActiveLayout. It loads only, and uses nothing.
True when a stored layout was found and taken. False covers all three ways there is nothing to take: no store, no id, and a store with nothing filed under this id. A caller needs them apart, because an empty ActiveLayout that came off disk means unsorted and an empty one that was never filled means nothing at all.
Split out because the callers really do differ about when the header and the model should hear about it. Building the widget has to load before the header is there, so a restored width is what the columns first draw at instead of a flash of the named one.
Reads whatever is filed under the id of this table again, and uses it.
One body for both ways in: a new id, and a new store. They are the same question asked twice.
The model SetItems fills. Built the first time it is needed, so a table with no items builds none.
The id of this row, or None with no model or no row.
Whether this row reads Added or Scrolled, for the cells and wrapper built for it.
Plays the entrances of any new rows now on screen, and drops the ones that ran out of time. Runs once a frame, and gives up at once while nothing is new.
Writes down whether the view is at the end, once a frame, before anything can add to the list.
Reads again any built row whose number now holds a different row than the one it drew.
The list keys its widgets by row number, and dropping a row retires the last number, not the dropped one.
So when the item set comes out the same length it went in, the list finds a widget for every number and asks none of them to read again. A capped feed adding one row and dropping one in the same frame is exactly that, and the table looks frozen until a scroll forces new widgets.
The row id spots the change. A row whose id has not moved is left alone.
Hides a wrapper that is owed an entrance nothing has seen yet.
Two call sites. AcquireRowWidget arms whenever the pool gives a wrapper out, new or reused. ReassignChangedRows arms when a wrapper that already exists takes a different row.
The row menu as a widget, from RowMenuEntries as they stand once OnRowMenuOpening has run.
Both routes in one place. An unset position means under the row, the answer with no pointer.
Starts the smooth move that every scroll to a row goes through. With no distance to cover it places the view on the row instead.
A presented row, never a natural one. Every caller already knows where it points in drawn order, and taking that keeps the search out of here.
One step of that move, off the timer the table already runs. The step that arrives hands over to PlaceScrollOnRow, since only the list can put the view exactly on a row.
Puts the view at the end once, for a table with bStartAtEnd set.
From the timer and never the constructor, which has neither of the two things it needs. The rows, which a model may not have given over yet, and a list that has been laid out.
Against a viewport of no height, far enough down to show the last row works out as the top.
Aims a move that is already going at wherever its row went, or stops it when the row is gone. RebuildRowIndices calls it, and that runs on every change to the row set there is.
The row at the top of the view, read before the rows are rebuilt. After the rebuild the built rows draw the new rows, and nothing is left that knows what the view showed.
Writes down the row at the top of the view once the rows are rebuilt, for the next change in this frame. See FSmartTableScrollMove::LeftAtTop
Scrolls the view once the rows are rebuilt after a row set change, whichever route the rows took. FSmartTableScrollMove::ScrollAfterRowSet decides. This carries it out.
Puts the held row back at the top of the view. Nothing moves when the row left the set.
The drawn place of the arrived row drawn last, or INDEX_NONE when none is drawn. A filter can hide every one of them.
Puts the view exactly on a row the move has already reached.
The last row is the odd one out, and the reason this is here. The target worked out is the row plus one, less the viewport over RowHeight, which is the true bottom only while every row is exactly RowHeight tall. One taller row and the target sits short of it.
RequestScrollIntoView will not fix that: it only promises the row is visible, and at that offset it already is. So for the last row the list gets asked where its own bottom is.
Says so, once, when this table is sized by its content instead of by its parent.
A list virtualises against the height it is given. Put one where the parent asks for a desired size instead, and the answer is the sum of every row. Every row then gets built.
Three parents do it: a Vertical Box slot left on Auto, a Scroll Box, a Size Box with no override. The table still draws correctly, and nothing on screen says anything is wrong.
Says so, once per id, when a column set names the same ColumnId twice.
The Blueprint compiler says this too, but a table built only from C++ never compiles a widget. That is exactly the case most likely to build its columns in a loop.
Writes HeaderStyle.LastColumnStyle from HeaderStyle.ColumnStyle. Called wherever HeaderStyle is written, which is every copy of the look.
Copies either the style asset or the named values into the inline style properties.
The copy is what lets every getter and Slate argument in this class point at one member and know nothing about style assets. It writes over the named values. Those go into AuthoredStyle on the first call and never again, or clearing the asset would leave the last style in place.
Pushes whatever the inline properties now hold out to the live Slate.
SetStyleAsset and the palette refresh both use it, because a style that comes in without this is only half done. STextBlock::SetTextStyle copies instead of keeping the pointer, so a cell built under the last look keeps that look until something sets it again.
Measures the row number gutter. Called wherever its inputs move: the presented row count, the text style the numbers draw in, and whether the gutter is shown at all.
Collapsed while bShowFocusBorder is off, and collapsed while neither this table nor anything inside it holds the keyboard.
Walks every row that still exists, and drops the ones that do not.
The clearing out is why this is one function and not a loop written at each call site. A row the list has thrown out leaves a dead weak pointer behind, and nothing else clears them, so every walk must.
The column an intent should work on, selecting it on the way through. Every column intent comes through here, so touching a column selects it is one rule in one place.
Broadcasts and redraws when bChanged is true. The one place a selection change becomes visible.
Events 10
Columns 1
Fires only when the aim really moves. Selecting the column already aimed at raises nothing.
Events 7
The whole selection, every time it changes. An empty array is what a cleared selection sends.
A click twice, or Enter on a focused row. Item is null when the model builds no objects.
The order the model settled on, which is not always the one that was asked for.
Fires only on a change the table itself did: a resize, a hide, a sort, a reset. Never on ApplyLayout, so code that saves on it can not loop against its own restore.
A control inside a cell moved.
The table says so and writes nothing. It gives back the item, the column and the new value. Writing that value and calling NotifyItemChanged is the owner's work.
Fires before the entries are read, so SetRowMenuEntries from here builds the menu for this row.
Which entry was picked, and on which row. The table itself does nothing with either.
Drag and Drop 2
A cell was dropped on another and nothing has moved. Moving the data is the owner's to do.
Rows were dropped and the table has already moved them.
Public variables 3
Adds to the right click menu of a column, after the built-in entries.
A plain C++ delegate, not a Blueprint one, because an FMenuBuilder payload has no reflected form.
The same way out for the row menu, added after the named entries.
The pinned index column. Nothing stops a column being authored with this id, and nothing warns. The odd name is the only guard.
Protected variables 68
Smart Table 16
The columns, in the order they were named. TitleProperty so a closed row reads as its ColumnId and not as Index 13, which says nothing until it is opened.
Which of the three fields below the columns of this table are written against.
The other two are hidden, not greyed out, and cleared when the mode moves off them. Two sources half set is not a pair to keep: whichever was read first would win, with nothing to say which.
- Editable when
- ColumnSource == ESmartTableColumnSource::ItemClass
The class the columns are written against. Optional, and the single most useful thing to set.
It fills the binding dropdown on every column, checks the bindings while they are being typed, and lets the designer preview draw real values from the class defaults.
A Blueprint class works the same as a C++ one, since bindings resolve by name.
- Editable when
- ColumnSource == ESmartTableColumnSource::DataTable
The DataTable the columns are written against. The other half of ExpectedItemClass.
Naming it changes nothing at runtime. What it does is check every column against the row struct when the asset compiles, and let the whole column set be built from it instead of typed out by hand.
Soft on purpose. A hard reference to a missing asset turns into null with no log line, which reads as never setting one. A soft path outlives the asset, so the table can say it is gone.
- Editable when
- ColumnSource == ESmartTableColumnSource::File
A .csv or .json whose shape the columns were written against, for data that is not an asset.
Read only when the button below is pressed. A packaged game never opens this path, so moving the file breaks nothing at runtime.
The picker stores a file inside the project relative to the project folder, and the button reads it from there. The same path then names the same file in every copy of the project.
Only the header row of a CSV and the first object of a JSON get read. Neither is a model, and the plugin ships no model for them.
Whether clicking a row selects it, and how many rows can be selected. None is for a table whose job is editing, where the row mark only gets in the way of the field being changed.
Whether a click can reach the widget inside a cell.
Off, a cell is hit test invisible and the row under it takes every click. That is what a table only ever selected from needs, and it is why a button inside a cell does nothing until this is on.
On, what a cell holds takes input first. Only the ORDER is decided here. A control that does not take the click lets it through to the row, so selection goes on working.
bAllowCellDragDrop makes cells hit testable whatever this says.
- Minimum
- 0.0
The least height every row gets, in pixels. Zero lets each row grow to the height of its own cells.
- Minimum
- 0.0
- AdvancedDisplay
- The panel hides it under the arrow.
How long a new row waits to be seen before the table drops its entrance. Seconds.
A new row can be off screen, and its entrance is still right a moment later. A minute later it is not.
Keep this above ScrollToRowSeconds, or a row still being scrolled to runs out of time on the way.
Keep a view that sits at the end at the end as rows arrive.
What a chat window does: scroll up and the table stays put, scroll back down and it follows again. Staying put holds with it off too. The row at the top of the view stays there while rows arrive below it or leave above it.
Off, the table scrolls to each new row instead, wherever the view sits and wherever the sort put the row. Neither happens with bScrollToAddedRow off.
Open at the end of the rows instead of the top.
For a log or a chat that gets all its rows at once. It fires once, on the first laid out frame that has rows. A table filled from empty never uses it, because its first row is already the end.
bStickToEnd can not do this: a table built holding more rows than fit opens at the top, which reads as scrolled up, so following never starts. ScrollToEnd is the same move for rows that come in later.
- Range
- 0.05 to 1.0
- AdvancedDisplay
- The panel hides it under the arrow.
How much of a row has to be on screen before its entrance plays. A fraction of the row height.
Not 1.0. A row is never exactly whole once rounding and a part pixel viewport are in play, so asking for all of it gives entrances that never fire. Not near zero either. A strip of a row two pixels high is what the fraction is here to rule out.
A widget wrapped around the cells of every row, so a row can be animated in Sequencer.
Empty by default: no wrapper is built and nothing is pooled, so a big table pays nothing for it. Name a class and each visible row gets one, reused as the list scrolls.
See USmartTableRowWidget for the named slot the cells go into.
How tall the table is. Fill is a list: it takes the height it is given and scrolls.
FitRows is a block of rows. It asks for all of them, so a parent that sizes to fit grows to show every one. Asking is all it does, and a parent that gives less still wins. The table goes back to scrolling, so no row ends up out of reach.
- Minimum
- 0
- Editable when
- HeightMode == ESmartTableHeight::FitRows
Fit Rows only. Zero asks for every row, and anything else shows up to that many and then scrolls.
Draw the header row. Off, the same columns draw as a plain list of cells, with no labels, no click to sort and no resizing.
Drag and Drop 6
Lets cells start drags and take drops. Off unless a table really moves things around.
The plugin does no dragging of its own. OnCellDropped says what happened and the owner decides.
Lets a row be dragged into a new place in the order. Needs a model that answers MoveRows. The built-in items model does, a DataTable one can not.
A press that goes nowhere still selects: a drag only starts past the engine's drag distance. Drag a row that is selected with several others and all of them move.
See MoveRowsTo
Whether the table marks the source and target cells itself during a drag.
On, a cell class gets the marking without drawing a thing itself. Off, the two brushes are never drawn, which is what a cell class drawing its own marking from USmartTableCell::OnCellDragRoleChanged needs.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
Drawn over the cell the drag started on. See through dark by default, since what it held is under the cursor now, and a cell that still looks full while its contents are moving looks like a copy.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
Drawn over the cell a drag is hovering, never instead of it. The cell goes on drawing itself underneath.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
Drawn in the gap a release would drop rows into. Its height is how thick the bar draws.
Row Numbers 3
A numbered column pinned to the far left.
It counts what is drawn, not the rows underneath. After a sort the numbers still read 1, 2, 3 down the screen, because a column of numbers that jumps around can not be used to point at the fourth one.
- Minimum
- 8.0
The least width the pinned number column gets, in pixels. It grows past this on its own once the row count needs more digits.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
The text in the pinned number column. Its own style, because a number column usually reads quieter than the data beside it.
Columns 8
The last visible column takes whatever width is left over.
Off by default, since a column whose width means something should keep the width it was given. A bar is one, and a fixed width code is another. On, the table has no ragged right edge, which is what a notes column needs.
A floor, not an override: drag that column wider than the leftover and it stays there.
What happens when the columns need more width than the table has.
Off, the columns fit. Widening one takes width from those to its right, down to MinColumnWidth each, and nothing is ever off screen. On, they keep their widths and a sideways scrollbar shows up. That is what a table with thirty columns needs.
bStretchLastColumn is not switched off here. Once the columns run past the edge it finds no leftover to hand out.
Drag a header onto another to move it. Off, no drag moves a column. The menu entries Move Left and Move Right go on working, and so do MoveColumn and MoveColumnNextTo.
Dragging the right edge of a column resizes it.
Off, no drag moves a width. A fixed HUD table needs that without having to clear bResizable on every column it names. SetColumnWidth, SizeColumnToContent and BeginColumnResize still work, so the game and a pad still set widths.
SHeaderRow draws its own sizing grip on every Manual column and offers no way to turn it off. The last few pixels of a locked column still show a resize cursor and still take the press, and the drag writes nothing. HandleColumnWidthChanged refuses it.
Which entries the built-in right click menu offers, and whether it opens at all.
- Minimum
- 2.0
How much of the right edge of a column counts as the resize grip, in pixels. Wider is simpler to hit and takes more of the column beside it.
The declaration carries no value on purpose, and the same goes for the two below. The constructor sets all three from USmartTableSettings before anything reads them, so a number written on those lines is written over first. The project settings decide what a new table opens with.
- Minimum
- 1.0
How narrow any column may go, in pixels. It is what stops a column being taken down to nothing while its neighbours pull width off it. The project settings decide what a new table opens with.
- Minimum
- 0.0
The widest a size to content column may go, in pixels. Zero lets it fit the longest value, no matter how wide that is.
Preview 1
- Range
- 0 to 24
How many rows the designer draws with no data, so the widget is not an empty box in the palette. Zero previews the empty message, which is the only way to see it without running the game.
Style 16
A style shared with every other table that uses it.
Set, it wins over every style property this table has, and each one greys out in the panel. Six of them are filed under Row Numbers, Empty State, Drag and Drop and Busy, not under Style.
A greyed value is not lost. It is what the table goes back to when the asset is cleared.
Restyling a project is then one asset instead of one Details panel per table.
- Editable when
- StyleAsset == nullptr
Drawn behind the rows, under everything else the table draws.
This one and the styles under it are the look a table always has, which is why they stay in plain sight. The brushes for one feature or another sit behind Advanced. A style asset writes over the lot of them either way.
- Editable when
- StyleAsset == nullptr
One row: its background, and how it draws while hovered, selected and striped.
- Editable when
- StyleAsset == nullptr
The header row: its background, the splitter, and the sort arrows.
- Editable when
- StyleAsset == nullptr
Both scrollbars, the one down the side and the one along the bottom.
- Editable when
- StyleAsset == nullptr
The text of the header labels. Kept apart from the cell text style.
- Editable when
- StyleAsset == nullptr
The text in every built-in text cell, and the editable cell's read only and typing text with them. No column overrides it. That pairing exists for the header style and not for this one. A cell class of its own is free to read it or not.
- Minimum
- 0.0
- Editable when
- StyleAsset == nullptr
Header height in pixels. Zero means take it from the style.
The declaration carries no value on purpose. The constructor reads the height off the style class with the rest of the look, so a number written on that line is written over before anything reads it. It would still look exactly like the live one.
See USmartTableStyle::HeaderHeight, where it lives once.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
The bar that shows where a dragged column will end up.
- Editable when
- StyleAsset == nullptr
The line between two columns. The header cell draws it, not the splitter, so its width is paid by the header and by no row.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
The bar under the column that column input is aimed at.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
Drawn around the whole table while it holds the keyboard.
Draw the focus border at all.
On by default. It is the table-wide answer to where the keyboard went, and focus moves for reasons nothing else shows: a menu closing, or a click taken by the widget above the table. The header's column caret says the same thing for one column, and only while a column is selected.
Draw the bar under the selected column at all.
It gates the drawing only. The selected column is still tracked and still answers every intent given no column id. This is for a UI that already shows the target some other way.
Hide the column caret while the keyboard is somewhere else.
Off, because a key bound through an input context reaches this table while the focus sits on the viewport, which is where it sits for most of a game. On is for a screen walked by tab, where the caret is saying which of several widgets the keys go to.
The focus border is not covered by this. It draws what focus is doing, so it tests focus always.
- Editable when
- StyleAsset == nullptr
The style both menus use, the header one and the row one. A menu draws in its own window, over whatever is behind the table, so none of the surfaces above can serve it.
Empty State 3
Shown when the table has no rows at all.
Shown when a filter matches no row. {0} is the filter text.
A different message from the one above on purpose. Nothing here and nothing matching look like the same blank box and are two different problems.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
The text of both messages above.
Layout 2
Set one and this table's column layout lives past the session, filed under the table id below. Left unset, nothing is written anywhere.
What a stored layout is filed under. Taken from the widget name in the designer, and editable after that.
It is taken again on ANY Details panel edit while a store is set and this is empty. So clearing it and touching another property fills it straight back in.
There is no quiet fallback to the widget path. A rename would either lose the stored layout or, worse, give a renamed table the layout of a different one.
Sorting 4
Clicking a header sorts by it.
Off, no header click or drag to sort does anything. One switch, instead of clearing bSortable on every column. SortByColumn and SetSortSpec still work, so a table can be sorted from the game UI alone.
The gates in this panel work the same way. Each covers the pointer and key routes only, and leaves the function itself open.
Shift clicking a header adds a second sort level. The header menu lists Then Sort By This either way. Off, the entry is drawn greyed.
Off is for a table where one order is the only one that means anything, and a second arrow would look like a bug.
A third click clears the sort and puts the rows back in the model's own order.
Let a second sort level go onto a column that could not move a row.
A second level only breaks ties in the first, so a primary with no ties leaves every other column with nothing to do. On, the click goes through and no row moves. Off, it is refused and the header dims.
A column that can still break a tie is never refused, and on an unsorted table the gesture becomes the primary. It costs one pass over the sorted rows, once Shift is held.
Rows 4
Scroll when rows arrive: to the end, or to the new row, as bStickToEnd says.
On, because a row added at the end sits past the bottom and the only sign of it is a shorter scrollbar. It scrolls and nothing else, so the selection stays where it was put.
Off for a table filled in the background, where pulling the view on every new row fights the reader.
Rows arrive through AddItem, through SetItems, and through a model that says NotifyRowsAdded. A set given all at once, or given to a table that drew no row yet, scrolls nothing.
- Minimum
- 0.0
How long any scroll to a row takes. Seconds. Zero jumps straight there.
It covers the one above, ScrollRowIntoView and ScrollItemIntoView alike. They move instead of jumping, because a view that changes between two frames says the list moved without saying where from.
bScrollToAddedRow does not gate it: a table that never scrolls by itself still scrolls when asked.
Whether a right click on a row opens anything. Off by default.
What that menu offers, in order.
Named here instead of built in code, so a menu that is the same on every row needs no C++. SetRowMenuEntries is the other half, for a menu whose entries depend on the row.
Input 2
Take the keyboard when a pointer touches this table.
On by default. Slate alone gives the keyboard to the nearest widget above the click that can take focus, which for a header or a gap is outside the table. Its arrows and Enter then go dead.
It gates the built-in binding only. FocusTable stays public, so a game can drive focus from its own UI.
Let the right mouse button drag the rows up and down.
Right for a long list on a touchpad, wrong wherever the right button already means something. A table whose cells take drops spends every right press working out if it was a scroll.
Off, only the right drag goes. ScrollRowIntoView, the wheel and touch all keep working.
Busy 3
- Minimum
- 0.0
How long work has to run before the table says it is busy. Seconds.
Not zero by default, since a spinner that only flashes up reads as a bug. Work that finishes inside the wait draws no spinner at all. A busy and not busy pair inside one call is the usual case of that.
The overlay takes the clicks instead of letting them reach a row that is moving. A table sorting again is about to change whatever row sits under the cursor.
- Editable when
- StyleAsset == nullptr
- AdvancedDisplay
- The panel hides it under the arrow.
Drawn over the whole table while it is busy.
Private variables 48
What GetAvailableColumnWidth() was the last time the widths were worked out, so TickTable can see when it moves. Negative until the first read, which no real width can be.
Read every frame instead of waiting on a delegate. The width has several causes: the parent resizing, a column being hidden, and the list's own scrollbar. That bar takes its width the moment the rows run past the bottom, which is after the first cells were set.
A wrapping cell is why it matters. The header reads its width every frame, but a cell works out its line breaks once, so a later move leaves it wrapped for a width that is gone.
The width every shown column has right now. ReconcileColumnWidths works them out, RebuildWidget empties the map when the Slate is built again, and everything else only reads: the header width attributes, the edge hit tests, the cell sizing.
Not a UPROPERTY and never written to disk, on purpose: these are worked out from other numbers, and a worked out number kept anywhere goes stale. A column not in here has not been worked out yet.
- Transient
- Never saved.
Owned, because items built in a Blueprint are held nowhere else. A table that only pointed at them would empty itself at the next garbage collection, in no fixed order.
- Transient
- Never saved.
- Transient
- Never saved.
- Transient
- Never saved.
SListView needs a style and not a brush. FTableViewStyle holds nothing but that brush, so naming it is naming the whole style and not part of one.
- Transient
- Never saved.
The stored overrides on top of the named columns. Empty means exactly as named.
The nothing to show line, kept so a style change can restyle it.
STextBlock copies the style it is given instead of keeping the pointer, so this one widget can not be refreshed by setting a member again the way a brush can. Something has to set it.
It is also the only surface of the table that nothing else rebuilds. Rows, cells and even the no columns placeholder all come back through RebuildHeader.
- Transient
- Never saved.
The engine's own pool, keyed by class. Held here instead of writing a second one.
- Transient
- Never saved.
The same pool again, for row wrappers. Kept apart from CellPool only because the two hold different classes and are freed at different moments. Stays empty while RowWidgetClass is empty.
The RowWidgetClass this table has already warned about. Null when it has warned about none.
Keyed by the class and not by a flag, because the class is the only input the answer depends on and it moves. Pointing RowWidgetClass at a second broken Widget Blueprint has to speak again, and a flag that has been set stays set for the life of the table.
A wrapper with no Columns slot is wrong for every row, so saying it per row would bury the log.
The new rows this table still owes an entrance to.
The rules are all in the type, which never holds this table, so each of them can be read with no widget and no model.
See USmartTableModel::NotifyRowsAdded, which is how a model says a row is new. See FSmartTableArrivals
The menu this table has open, and the style it draws from.
The order the two come down in is held by the type, so no caller here has to remember it.
Whether the height warning has already been given once for this table.
SetHeightMode and SetRowHeight reset it, because those are the two inputs the warning's own advice names: set MaxVisibleRows, or use HeightMode Fill. A second wrong answer has to speak again.
One input can not be in the key, and it is named here instead of hidden: whether the parent sizes to its content. That lives in a Slate slot owned by something else, with no moment this class can watch, so moving the table under another such panel resets nothing.
The move going on, where the view sits, and whether the opening scroll is paid.
The rules are all in the type, which never holds this table, so each of them can be read with no widget, no list and no model.
What the gutter last measured, before RowNumberColumnWidth is taken as a least against it. Written only by MeasureRowNumberWidth, which every path that can move it calls.
- Transient
- Never saved.
The style asset the inline properties hold a copy of right now.
SynchronizeProperties runs after every Details panel edit, and copying the asset there every time would put the named snapshot back over an inline style property just typed in. Reading this is what separates a change of asset from any other change on the table.
- Transient
- Never saved.
The named look, taken once before any style asset is copied over it.
Ticks since WarnIfSizedByContent last looked. It walks to the window, which is a parent chain, and the shape it looks for arrives with a resize or a new layout, never inside one frame.
Whether the row menu has already said it has nothing to show. Cleared by SetRowMenuEntries.
Two things make the menu whole and only one of them is keyed here. Binding OnExtendRowMenu is the other, and a delegate offers no moment to clear a flag from, so a table that binds it and then drops the binding says nothing about the second empty menu.
The rows the list has really built. Weak, and cleared out as they go.
Pinned outside the sideways scroll box. A list builds a bar of its own inside the scrolled content. On a table wider than its viewport that one sits off to the right, out of reach until the view has scrolled that far.
The viewport the columns are read against, and what a resize scrolls when it runs off the edge.
So the no item column warning is said once per column, and not once per cell, per scroll.
The class each column was last warned about, so a broken CellClass is said once per column and not once per cell, per scroll.
Two inputs decide the answer and both are in the key: the column, and the class it names. A Details panel edit reaches neither SetColumns nor any other reset, so the column alone would go quiet the moment CellClass moved to a second broken class.
See RowWidgetSlotWarnedFor, the same member for the row wrapper, for the same reason.
The cell classes whose ItemSetter mistake this table has said. Keyed by the class alone, since the mistake belongs to the class. A Blueprint compiled again is a new class and is said again.
The missing TableId warning is about the table, not about the change that set it off. Every drag, hide and sort would otherwise say it again for the rest of the session.
Guards the rebuild and the broadcast. Code that sorts from inside a sort handler would otherwise call back into itself, and a model with no work dispatcher set sorts inside the same call. Nothing in the plugin sets one, so that is what every table does until a game says otherwise.
Set while MoveRowsTo runs its rebuilds. Each rebuild puts the selection back, and a sorted drop runs two of them. HandleSelectionChanged holds its broadcast back while this is set.
A broadcast the hold above kept back. MoveRowsTo sends it once, after the rows are in place.
A row is part way through walking its own cells right now. FCellWalkScope is the only writer, so there is nothing to key and nothing to leave set.
A cell event is a Blueprint event. OnCellAssigned or OnCellReleased can call SetColumnVisible, and SHeaderRow::ClearColumns broadcasts straight back into GenerateWidgetForColumn on the row that is still walking. The copy a walk takes is safe to READ, and that is all. Inside the broadcast a cell can be released, given to another row, then given this row's number by a walk on a stale copy.
Not bApplyingPresentation, which guards a model broadcast re-entering itself.
A header rebuild was turned back by that guard and is still owed.
There is nothing to key it by. A rebuild reads every input again when it runs, and the one fact worth remembering is that a rebuild is owed at all.
TickTable never stops, so the owed rebuild cannot be dropped on the floor. bColumnWidthsDirty and bLayoutSavePending are the same shape.
RecomputeDeadSecondaryColumns is the only writer. RefreshDeadSecondaryColumns is a dirty gated wrapper over it for the drawing readers, and neither is const. Every reader runs one of them first, which is what lets the BlueprintPure getter stay a plain read.
False until the first pass rebuilt the header with every resizable column in Manual mode.
Before that the header holds the named Fixed and Fill modes, because a bound attribute would answer in fill weights until a pass has run. The first frame would be one pixel wide.
It is not a flag on the pass itself, which keeps running for the whole life of the table. Once per SLATE rebuild, though, and not once per object: RebuildWidget puts this back to false, and a table can be rebuilt more than once.
The row number width the header was built with. FixedWidth is a Slate argument and not an attribute, so a gutter that grows past it can only be widened by a rebuild.
The row the caret is on, in natural space so it lives through a sort the way a selection does.
This is a second caret. SListView keeps its own, and the two have to agree, or Space and a pad move would work around different rows.
MoveSelection keeps them together by selecting with ESelectInfo::OnNavigation. The engine only moves its own caret for a selection that is not Direct, which is why SetRowSelected leaves both alone.
The aim, and the rules that move it. A plain value type with no widget in reach.
The doubled column ids this table has already warned about.
Keyed by the ids themselves and not by a flag, because those ids are the whole input the answer depends on. Fix one and leave another, and the table has something new to say. SetColumns is the only writer, so an entry can not outlive the column set it is about.
The resize going on now. Both routes into a resize write it, and one Reset ends either.
When busy was set. GetBusyOverlayVisibility reads it against BusyOverlayDelaySeconds every time the overlay is asked to draw, so the wait needs no timer of its own.
Delegate types 12
The selection, once it changed. One broadcast per change, carrying the whole set.
Rows and not items. A model that builds no items gives back null for every one of them, and GetSelectedItems is one call away where items do exist.
A cleared selection arrives as an empty array. Nothing else marks it.
A click twice, or Enter on a focused row. Item is null over a model that builds none, so the row comes with it.
The column the table is aimed at changed. NAME_None means the aim was cleared.
A control inside a cell moved. The table writes nothing, because where the value lives is not something it can know.
It carries the row as well as the item. A model that builds no items still has rows to edit, and Item is null for every one of them.
A cell was dropped onto another cell, and nothing has moved.
The table says what happened and the owner moves the data, the same way a sort works. A drop might mean a swap, a new row, or nothing at all, and only the owner knows which.
Both rows are natural, numbered as the rows stand when the drop lands.
The payload's SourceTable may not be this table. A cell drop from ANOTHER Smart Table is taken: a cell names a column and not a place in an order. A ROW drop across tables is refused, because two tables over the same rows mean their numbers differently. SourceTable says which kind arrived.
Rows were dropped and the table has ALREADY moved them. This is the opposite of OnCellDropped, which moves nothing at all: a reorder goes through MoveRows, and the table can run that itself.
Both numbers name rows as they stood before the move. An owner keeping its own copy of the order needs them that way round.
A row's menu is about to be built. SetRowMenuEntries runs from here for a menu that depends on the row.
It fires before the entries are read, not after. One named list can then serve a menu whose entries differ per row. The menu is built from the list as it stands when the event returns.
Which entry was picked, on which row. The table runs nothing off it.
The row and its item are read as the entry is picked. A row that moved while the menu was open comes at its new number, and a row that left raises nothing.
Both the id and the index arrive, because a handler written as a switch takes one or the other.
EntryIndex is the place in RowMenuEntries as named, so it holds still when an entry hides itself. A handler keyed on the drawn place works on the wrong entry the moment a row hides one.
The column the menu was opened on, and the builder to add entries to.
The C++ way out for a row menu that needs more than a list of entries: submenus, icons, checks.
The row comes kept. Its NaturalRow is right while the menu is built, and an entry picked later finds the row again with FindKeptRow on the model.
FCellWalkScope struct
Held by a row for as long as it is walking its own cells, so a header rebuild knows to wait.
Not part of the API. Only this plugin's own rows hold one, and it is public only because those rows are Slate widgets and not members of this class.
A scope, never a pair of calls. An early return, a throw, or a cell event that never comes back would each leave a plain flag set, and the header frozen for the life of the table. Two of them can sit inside each other, because it puts back what it found instead of clearing.
Constructors and destructor 4
Private variables 2
Weak, because a row outlives its table by a frame during teardown and ReleaseCells runs then.
Next: SmartTableTypes.h