Files
lazarus-ccr/components/nvidia-widgets/src/nvcontext.pas

1120 lines
34 KiB
ObjectPascal
Raw Normal View History

//
// nvWidgets.h - User Interface library
//
//
// Author: Ignacio Castano, Samuel Gateau, Evan Hart
// Email: sdkfeedback@nvidia.com
//
// Copyright (c) NVIDIA Corporation. All rights reserved.
////////////////////////////////////////////////////////////////////////////////
unit nvContext;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, nvTypes, nvPainter, nvBaseFont;
type
{ UIContext }
UIContext = class(TObject)
public
constructor Create;
destructor Destroy; override;
// UI method for processing window size events
//////////////////////////////////////////////////////////////////
procedure reshape(w, h: integer);
//
// Check if the UI is currently on Focus
//////////////////////////////////////////////////////////////////
function isOnFocus: boolean;
//
// UI method for processing mouse events
//////////////////////////////////////////////////////////////////
procedure mouse(button, state, modifier, x, y: integer);
procedure mouse(button, state, x, y: integer);
//
// UI method for processing mouse motion events
//////////////////////////////////////////////////////////////////
procedure mouseMotion(x, y: integer);
// UI method for processing key events
//////////////////////////////////////////////////////////////////
procedure keyboard(k: byte; x, y: integer);
//
// UI method for entering UI processing mode
//
// This function must be used to begin the UI processing
//////////////////////////////////////////////////////////////////
procedure _begin;
// UI method for leaving UI processing mode
//
// This function must be used to end the UI processing
//////////////////////////////////////////////////////////////////
procedure _end; virtual;
////////////////////////////////////////////////////////////////////////////
// UI element processing
//
// The following methods provide the interface for rendering and querying
// UI objects. These methods must be called between begin/end.
////////////////////////////////////////////////////////////////////////////
//
// UI method for drawing a static text label
// The label display a non interactive text.
// The text can have multiple lines
//
// rect - optionally provides a location and size for the label
// text - Text to display for the label (can have several lines)
//////////////////////////////////////////////////////////////////
procedure doLabel(const r: Rect; const Text: string; style: integer = 0);
//
// UI method for rendering and processing a push button
//
// rect - optionally provides a location and size for the button
// text - text to display on the button
// state - whether the button is depressed
// if state is NULL; the buttoin behave like a touch button
// else; the button behave like a toggle button
// style - optional style flag to modify the look
//
// @return True if the button'state changed
//////////////////////////////////////////////////////////////////
function doButton(const r: Rect; const Text: string; var state: boolean; style: integer = 0): boolean;
function doButton(const r: Rect; const Text: string): boolean;
// UI method for rendering and processing a check button
// Check button behaves similarly as a toggle button
// used to display and edit a bool property.
//
// rect - optionally provides a location and size for the button
// text - text to display on the button
// state - whether the check button is checked or not
// if state is NULL; the button behave like if a touch button unchecked
// style - optional style flag to modify the look
//
// @return True if the check button state changed
//////////////////////////////////////////////////////////////////
function doCheckButton(const r: Rect; const Text: string; var state: boolean; style: integer = 0): boolean;
//
// UI method for rendering and processing a radio button
// Radio buttons are typically used in groups to diplay and edit
// the possible reference values taken by an int value.
//
// One radio button is representing a possible reference value taken by the current value.
// It is displaying a boolean state true if the current value is equal to the reference value.
//
// reference - The reference int value represented by this radio button.
// rect - optionally provides a location and size for the button
// text - text to display on the button
// value - The value parameter compared to the reference value parameter.
// if value is NULL; the radio button is off
// style - optional style flag to modify the look
//
// @return True if the radio button value changed
//////////////////////////////////////////////////////////////////
function doRadioButton(reference: integer; const r: Rect; const Text: string; var Value: integer; style: integer = 0): boolean;
//
// UI method for rendering and processing a horizontal slider
// Horizontal slider is used to edit and display a scalar value in the specified range [min; max].
//
// rect - optionally provides a location and size for the widget
// min - min bound of the varying range of the value
// max - max bound of the varying range of the value
// value - the value edited by the widget
// if value is NULL; the value is set to min
// style - optional style flag to modify the look
//
// @return True if the slider value changed
//////////////////////////////////////////////////////////////////
function doHorizontalSlider(const aRect: Rect; min: double; max: double; var Value: double; style: integer = 0): boolean;
function doListItem(index: integer; const aRect: Rect; const Text: string; selected: integer; style: integer = 0): boolean;
function doListBox(const aRect: Rect; numOptions: integer; const options: array of string; var selected: integer; style: integer = 0): boolean;
function doComboBox(const aRect: Rect; numOptions: integer; const options: array of string; var selected: integer; style: integer = 0): boolean;
function doLineEdit(const aRect: Rect; var Text: string; maxTextLength: integer; out nbCharsReturned: integer; style: integer = 0): boolean;
procedure beginGroup(groupFlags: integer; const r: Rect); overload;
procedure beginGroup(groupFlags: integer = GroupFlags_LayoutDefault); overload;
procedure endGroup;
procedure beginFrame(groupFlags: integer = GroupFlags_LayoutDefault; const rect: Rect = 0; style: integer = 0);
procedure endFrame;
function beginPanel(var r: Rect; const Text: string; var isUnfold: boolean; groupFlags: integer = GroupFlags_LayoutDefault; style: integer = 0): boolean;
procedure endPanel;
function getGroupWidth: integer;
function getGroupHeight: integer;
function getCursorX: integer;
function getCursorY: integer;
function getMouseState(button: integer): ButtonState;
//
// UI method for drawing a texture view
// Several parameters control the equation used to display the texel
// texel = texture2DFetch(...);
// pixel = texelSwizzling( texel * texelScale + texelOffset );
//
// rect - provides a location and size for the texture view
// texID - texture identifier (Graphics API specific)
// zoomRect - rectangle area of the texture displayed
// mipLevel - mip Level of the texture displayed
// texelScale - scale applyed to the texel fetch
// texelOffset - offset applyed to the texel after scale
// texelSwizzling - swizzle applyed to the texel (after scale and offset)
// style - optional style flag to modify the look
//////////////////////////////////////////////////////////////////
procedure doTextureView(const aRect: Rect; const texID: integer; zoomRect: Rect; mipLevel: integer = -1; texelScale: double = 1; texelOffset: double = 0; red: integer = 0; green: integer = 1; blue: integer = 2; alpha: integer = 3; style: integer = 0);
protected
function getPainter: UIPainter;
function window: Rect;
private
FPainter: UIPainter;
procedure setCursor(x: integer; y: integer);
function overlap(const aRect: Rect; const p: Point): boolean;
function hasFocus(const aRect: Rect): boolean;
function isHover(const aRect: Rect): boolean;
procedure SetPainter(AValue: UIPainter);
protected
function placeRect(const r: Rect): Rect;
published
property Painter: UIPainter read FPainter write SetPainter;
protected
m_groupIndex: integer;
m_groupStack: array [0..63] of Group;
m_window: Rect;
m_currentCursor: Point;
m_mouseButton: array [0..2] of ButtonState;
m_keyBuffer: array [0..31] of byte;
m_nbKeys: integer;
m_focusCaretPos: integer;
m_focusPoint: Point;
m_twoStepFocus: boolean;
m_uiOnFocus: boolean;
end;
function EvalBool(b: boolean): byte;
implementation
uses
Math;
function EvalBool(b: boolean): byte;
begin
if b then
Result := 1
else
Result := 0;
end;
{ UIContext }
constructor UIContext.Create;
begin
m_twoStepFocus := False;
m_focusCaretPos := -1;
end;
destructor UIContext.Destroy;
begin
if Assigned(Painter) then
Painter.Free;
inherited Destroy;
end;
procedure UIContext.reshape(w, h: integer);
begin
m_window.x := 0;
m_window.y := 0;
m_window.w := w;
m_window.h := h;
end;
function UIContext.isOnFocus: boolean;
begin
Result := m_uiOnFocus;
end;
procedure UIContext.mouse(button, state, modifier, x, y: integer);
var
idx: integer;
begin
setCursor(x, y);
idx := -1;
case button of
MouseButton_Left: idx := 0;
MouseButton_Middle: idx := 1;
MouseButton_Right: idx := 2;
end;
modifier := modifier and (ButtonFlags_Alt or ButtonFlags_Shift or ButtonFlags_Ctrl);
if idx >= 0 then
begin
if state = 1 then
begin
m_mouseButton[idx].state := ButtonFlags_On or ButtonFlags_Begin or modifier;
m_mouseButton[idx].time := Now;
m_mouseButton[idx].cursor.x := x;
m_mouseButton[idx].cursor.y := m_window.h - y;
end;
if state = 0 then
m_mouseButton[idx].state := ButtonFlags_On or ButtonFlags_End or modifier;
end;
end;
procedure UIContext.mouse(button, state, x, y: integer);
begin
mouse(button, state, 0, x, y);
end;
procedure UIContext.mouseMotion(x, y: integer);
begin
setCursor(x, y);
end;
procedure UIContext.keyboard(k: byte; x, y: integer);
begin
setCursor(x, y);
m_keyBuffer[m_nbKeys] := k;
Inc(m_nbKeys);
end;
procedure UIContext._begin;
begin
Painter._begin(m_window);
m_groupIndex := 0;
m_groupStack[m_groupIndex].flags := GroupFlags_LayoutNone;
m_groupStack[m_groupIndex].margin := Painter.getCanvasMargin;
m_groupStack[m_groupIndex].space := Painter.getCanvasSpace;
m_groupStack[m_groupIndex].bounds := m_window;
end;
procedure UIContext._end;
var
i: integer;
begin
Painter._end;
// Release focus.
if (m_mouseButton[0].state and ButtonFlags_End) > 0 then
m_uiOnFocus := False;
// Update state for next frame.
for i := 0 to 2 do
begin
if (m_mouseButton[i].state and ButtonFlags_Begin) > 0 then
m_mouseButton[i].state := m_mouseButton[i].state xor ButtonFlags_Begin;
//else
if (m_mouseButton[i].state and ButtonFlags_End) > 0 then
m_mouseButton[i].state := ButtonFlags_Off;
end;
// Flush key buffer
m_nbKeys := 0;
end;
procedure UIContext.doLabel(const r: Rect; const Text: string; style: integer);
var
rt: Rect;
nbLines: integer;
aRect: Rect;
begin
aRect := placeRect(Painter.getLabelRect(r, Text, rt, nbLines));
Painter.drawLabel(aRect, Text, rt, nbLines, isHover(aRect), style);
end;
function UIContext.doButton(const r: Rect; const Text: string; var state: boolean; style: integer): boolean;
var
rt: Rect;
aRect: Rect;
focus: boolean;
hover: boolean;
isDown: boolean;
begin
aRect := placeRect(Painter.getButtonRect(r, Text, rt));
focus := hasFocus(aRect);
hover := isHover(aRect);
isDown := state;
//isDown := ((m_mouseButton[0].state and ButtonFlags_On)>0) and hover and focus;
Painter.drawButton(aRect, Text, rt, isDown, hover, focus, style);
if not focus then
m_uiOnFocus := True;
if ((m_mouseButton[0].state and ButtonFlags_End) > 0) and focus and overlap(aRect, m_currentCursor) then
begin
state := not state;
Result := True;
exit;
end;
Result := False;
end;
function UIContext.doButton(const r: Rect; const Text: string): boolean;
var
tmp: boolean = False;
begin
Result := doButton(r, Text, tmp);
end;
function UIContext.doCheckButton(const r: Rect; const Text: string; var state: boolean; style: integer): boolean;
var
rt: Rect;
rc: Rect;
aRect: Rect;
focus: boolean;
hover: boolean;
begin
aRect := placeRect(Painter.getCheckRect(r, Text, rt, rc));
focus := hasFocus(aRect);
hover := isHover(aRect);
Painter.drawCheckButton(aRect, Text, rt, rc, state, hover, focus, style);
if hasFocus(aRect) then
m_uiOnFocus := True;
if ((m_mouseButton[0].state and ButtonFlags_End) > 0) and focus and overlap(aRect, m_currentCursor) then
begin
state := not state;
Result := True;
exit;
end;
Result := False;
end;
function UIContext.doRadioButton(reference: integer; const r: Rect; const Text: string; var Value: integer; style: integer): boolean;
var
rr: Rect;
rt: Rect;
aRect: Rect;
focus: boolean;
hover: boolean;
begin
aRect := placeRect(Painter.getRadioRect(r, Text, rt, rr));
focus := hasFocus(aRect);
hover := isHover(aRect);
Painter.drawRadioButton(aRect, Text, rt, rr, longbool(Value and EvalBool(reference = Value)), hover, focus, style);
if focus then
m_uiOnFocus := True;
if ((m_mouseButton[0].state and ButtonFlags_End) > 0) and focus and overlap(aRect, m_currentCursor) then
begin
Value := reference;
Result := True;
exit;
end;
Result := False;
end;
function UIContext.doHorizontalSlider(const aRect: Rect; min: double; max: double; var Value: double; style: integer): boolean;
var
f: double;
rs: Rect;
rc: Rect;
rr: Rect;
changed: boolean = False;
xs: integer;
x: integer;
begin
// Map current value to 0-1.
f := (Value - min) / (max - min);
if f < 0 then
f := 0
else
if f > 1 then
f := 1;
rr := placeRect(Painter.getHorizontalSliderRect(aRect, rs, f, rc));
if hasFocus(rr) then
begin
m_uiOnFocus := True;
xs := rr.x + rs.x + rc.w div 2;
x := m_currentCursor.x - xs;
if x < 0 then
x := 0
else
if x > rs.w then
x := rs.w;
rc.x := x;
f := x / rs.w;
f := f * (max - min) + min;
if abs(f - Value) > (max - min) * 0.01 then
begin
changed := True;
Value := f;
end;
end;
Painter.drawHorizontalSlider(rr, rs, f, rc, isHover(rr), style);
Result := changed;
end;
function UIContext.doListItem(index: integer; const aRect: Rect; const Text: string; selected: integer; style: integer): boolean;
var
rt: Rect;
r: Rect;
begin
r := placeRect(Painter.getItemRect(aRect, Text, rt));
Painter.drawListItem(r, Text, rt, longbool(selected and EvalBool(index = selected)), isHover(r), style);
Result := isHover(r);
end;
function UIContext.doListBox(const aRect: Rect; numOptions: integer; const options: array of string; var selected: integer; style: integer): boolean;
var
ri: Rect;
rt: Rect;
rr: Rect;
focus: boolean;
hover: boolean;
hovered: integer = -1;
lSelected: integer = -1;
begin
rr := placeRect(Painter.getListRect(aRect, numOptions, options, ri, rt));
focus := hasFocus(rr);
hover := isHover(rr);
if hover then
hovered := numOptions - 1 - (m_currentCursor.y - (rr.y + ri.y)) div (ri.h);
if selected <> 0 then
lSelected := selected;
Painter.drawListBox(rr, numOptions, options, ri, rt, lSelected, hovered, style);
if focus then
m_uiOnFocus := True;
if ((m_mouseButton[0].state and ButtonFlags_End) > 0) and focus and overlap(rr, m_currentCursor) and (lSelected <> hovered) then
begin
selected := hovered;
Result := True;
exit;
end;
Result := False;
end;
function UIContext.doComboBox(const aRect: Rect; numOptions: integer; const options: array of string; var selected: integer; style: integer): boolean;
var
rt: Rect;
ra: Rect;
rr: Rect;
focus: boolean;
hover: boolean;
ro: Rect;
ri: Rect;
rit: Rect;
hovered: integer;
hoverOptions: boolean;
begin
// First get the rect of the combobox itself and do some test with it
rr := placeRect(Painter.getComboRect(aRect, numOptions, options, selected, rt, ra));
focus := hasFocus(rr);
hover := isHover(rr);
if focus then
begin
m_uiOnFocus := True;
// then if the combo box has focus, we can look for the geometry of the options frame
ro := Painter.getComboOptionsRect(rr, numOptions, options, ri, rit);
hovered := -1;
hoverOptions := overlap(ro, m_currentCursor);
if hoverOptions then
hovered := numOptions - 1 - (m_currentCursor.y - (ro.y + ri.y)) div (ri.h);
// draw combo anyway
Painter.drawComboBox(rr, numOptions, options, rt, ra, selected, hover, focus, style);
// draw options
Painter.drawComboOptions(ro, numOptions, options, ri, rit, selected, hovered, hover, focus, style);
// When the widget get the focus, cache the focus point
if not m_twoStepFocus then
begin
if hover and ((m_mouseButton[0].state and ButtonFlags_End) > 0) then
begin
m_focusPoint := m_mouseButton[0].cursor;
m_twoStepFocus := True;
end;
end
else
begin
// Else Release the 2level focus when left mouse down out or up anyway
// replace the stored left mouse down pos with the focus point to keep focus
// on this widget for the next widget rendered in the frame
if not (hoverOptions or hover) and
(((m_mouseButton[0].state and ButtonFlags_Begin) > 0) or
((m_mouseButton[0].state and ButtonFlags_End > 0))) then
m_twoStepFocus := False
else
if (hoverOptions or hover) and ((m_mouseButton[0].state and ButtonFlags_End) > 0) then
begin
m_mouseButton[0].cursor := m_focusPoint;
m_twoStepFocus := False;
end;
if hoverOptions and ((m_mouseButton[0].state and ButtonFlags_Begin) > 0) then
m_mouseButton[0].cursor := m_focusPoint;
end;
// On mouse left bouton up, then select it
if (hovered > -1) and (hovered < numOptions) and ((m_mouseButton[0].state and ButtonFlags_End) > 0) then
begin
selected := hovered;
Result := True;
exit;
end;
end
else
Painter.drawComboBox(rr, numOptions, options, rt, ra, selected, hover, focus, style);
Result := False;
end;
function UIContext.doLineEdit(const aRect: Rect; var Text: string; maxTextLength: integer; out nbCharsReturned: integer; style: integer): boolean;
var
rt: Rect;
rr: Rect;
focus: boolean;
hover: boolean;
_result: boolean = False;
carretPos: integer = -1;
textLength: integer;
nbKeys: integer;
keyNb: integer;
begin
rr := placeRect(Painter.getLineEditRect(aRect, Text, rt));
focus := hasFocus(rr);
hover := isHover(rr);
if focus then
begin
m_uiOnFocus := True;
// When the widget get the focus, cache the focus point
if not m_twoStepFocus then
begin
m_twoStepFocus := True;
m_focusPoint := SetPoint(rr.x, rr.y);
end
else
begin
// Else Release the 2level focus when left mouse down out or up anyway
// replace the stored left mouse down pos with the focus point to keep focus
// on this widget for the next widget rendered in the frame
if not hover and
(((m_mouseButton[0].state and ButtonFlags_Begin) > 0) or
((m_mouseButton[0].state and ButtonFlags_End) > 0)) then
begin
m_twoStepFocus := False;
m_focusCaretPos := -1;
end;
if hover and ((m_mouseButton[0].state and ButtonFlags_Begin) > 0) then
m_mouseButton[0].cursor := m_focusPoint;
end;
// Eval caret pos on every click hover
if hover and ((m_mouseButton[0].state and ButtonFlags_Begin) > 0) then
m_focusCaretPos := Painter.getPickedCharNb(Text, SetPoint(m_currentCursor.x - rt.x - rr.x, m_currentCursor.y - rt.y - rr.y));
// If keys are buffered, apply input to the edited text
if m_nbKeys <> 0 then
begin
textLength := Length(Text);
if m_focusCaretPos = -1 then
m_focusCaretPos := textLength;
nbKeys := m_nbKeys;
keyNb := 0;
while nbKeys <> 0 do
begin
// filter for special keys
// Enter, quit edition
if m_keyBuffer[keyNb] = 13 then
begin
nbKeys := 1;
m_twoStepFocus := False;
m_focusCaretPos := -1;
end
else
// Special keys
if m_keyBuffer[keyNb] >= Key_F1 then
begin
case m_keyBuffer[keyNb] of
Key_Left:
// move cursor left one char
if m_focusCaretPos > 0 then
Dec(m_focusCaretPos);
Key_Right:
// move cursor right one char
if m_focusCaretPos <= textLength then
Inc(m_focusCaretPos);
Key_Home:
m_focusCaretPos := 0;
Key_End:
m_focusCaretPos := textLength + 1;
Key_Insert:
begin
end
else
// strange key pressed...
Dec(m_focusCaretPos);
end;
end
else
// Delete, move the chars >= carret back 1, carret stay in place
if m_keyBuffer[keyNb] = 127 then
Delete(Text,m_focusCaretPos, 1)
else
// Backspace, move the chars > carret back 1, carret move back 1
if m_keyBuffer[keyNb] = 8 then
begin
if m_focusCaretPos > 0 then
begin
Delete(Text,m_focusCaretPos-1, 1);
Dec(m_focusCaretPos);
Dec(textLength);
end;
end
else
// Regular char, append it to the edited string
if textLength < maxTextLength then
begin
Insert(Chr(m_keyBuffer[keyNb]), Text, m_focusCaretPos);
Inc(m_focusCaretPos);
Inc(textLength);
end;
Inc(keyNb);
Dec(nbKeys);
end;
nbCharsReturned := textLength;
_result := True;
end;
carretPos := m_focusCaretPos;
end;
Painter.drawLineEdit(rr, Text, rt, carretPos, focus, hover, style);
Result := _result;
end;
procedure UIContext.beginGroup(groupFlags: integer; const r: Rect);
var
parentGroup: Group;
newGroup: PGroup;
parentLayout: integer;
parentAlign: integer;
newAlign: integer;
newStart: integer;
//newLayout: integer;
aRect: Rect;
begin
// Push one more group.
parentGroup := m_groupStack[m_groupIndex];
Inc(m_groupIndex);
newGroup := @m_groupStack[m_groupIndex];
// Assign layout behavior
parentLayout := parentGroup.flags and GroupFlags_LayoutMask;
parentAlign := parentGroup.flags and GroupFlags_AlignMask;
// If the groupFlags ask to force the layout then keep the newcanvas layout as is
// otherwise, adapt it to the parent's behavior
if ((groupFlags and GroupFlags_LayoutForce) = 0) or ((groupFlags and GroupFlags_LayoutNone) = 0) then
begin
// If default then use parent style except if none layout => default fallback
if (groupFlags and GroupFlags_LayoutDefault) > 0 then
begin
if (parentLayout and GroupFlags_LayoutNone) > 0 then
groupFlags := GroupFlags_LayoutDefaultFallback
else
groupFlags := parentGroup.flags;
end
else
if ((parentLayout and (GroupFlags_LayoutVertical or GroupFlags_LayoutHorizontal)) > 0) and
((groupFlags and (GroupFlags_LayoutVertical or GroupFlags_LayoutHorizontal)) > 0) then
groupFlags := (groupFlags and GroupFlags_AlignXMask) or parentAlign;
end;
newGroup^.margin := EvalBool((groupFlags and GroupFlags_LayoutNoMargin) = 0) * Painter.getCanvasMargin;
newGroup^.space := EvalBool((groupFlags and GroupFlags_LayoutNoSpace) = 0) * Painter.getCanvasSpace;
newGroup^.flags := groupFlags;
//newLayout := groupFlags and GroupFlags_LayoutMask;
newAlign := groupFlags and GroupFlags_AlignMask;
newStart := groupFlags and GroupFlags_StartMask;
// Place a regular rect in current group, this will be the new group rect start pos
aRect := r;
// Don't modify parent group bounds yet, done in endGroup
// Right now place the new group rect
if parentLayout = GroupFlags_LayoutNone then
begin
// Horizontal behavior.
aRect.x := aRect.x + (parentGroup.bounds.x + newGroup^.margin + EvalBool((newStart and GroupFlags_StartRight) > 0) * parentGroup.bounds.w - EvalBool((newAlign and GroupFlags_AlignRight) > 0) * (2 * newGroup^.margin + aRect.w));
// Vertical behavior.
aRect.y := aRect.y + (parentGroup.bounds.y + newGroup^.margin + EvalBool((newStart and GroupFlags_StartTop) > 0) * parentGroup.bounds.h - EvalBool((newAlign and GroupFlags_AlignTop) > 0) * (2 * newGroup^.margin + aRect.h));
end
else
if parentLayout = GroupFlags_LayoutVertical then
begin
// Horizontal behavior.
aRect.x := aRect.x + (parentGroup.bounds.x + newGroup^.margin + EvalBool((parentAlign and GroupFlags_AlignRight) > 0) * (parentGroup.bounds.w - 2 * newGroup^.margin - aRect.w));
// Vertical behavior.
if (parentAlign and GroupFlags_AlignTop) > 0 then
aRect.y := aRect.y + (parentGroup.bounds.y - (EvalBool(parentGroup.bounds.h > 0) * parentGroup.space) - newGroup^.margin - aRect.h)
else
aRect.y := aRect.y + (parentGroup.bounds.y + parentGroup.bounds.h + EvalBool(parentGroup.bounds.h > 0) * parentGroup.space + newGroup^.margin);
end
else
if parentLayout = GroupFlags_LayoutHorizontal then
begin
// Horizontal behavior.
if (parentAlign and GroupFlags_AlignRight) > 0 then
aRect.x := aRect.x + (parentGroup.bounds.x - (EvalBool(parentGroup.bounds.w > 0) * parentGroup.space) - newGroup^.margin - aRect.w)
else
aRect.x := aRect.x + (parentGroup.bounds.x + parentGroup.bounds.w + EvalBool(parentGroup.bounds.w > 0) * parentGroup.space + newGroup^.margin);
// Vertical behavior.
aRect.y := aRect.y + (parentGroup.bounds.y + newGroup^.margin + EvalBool((parentAlign and GroupFlags_AlignTop) > 0) * (parentGroup.bounds.h - 2 * newGroup^.margin - aRect.h));
end;
newGroup^.bounds := aRect;
end;
procedure UIContext.beginGroup(groupFlags: integer);
var
r: Rect;
begin
r.Rect(0, 0, 0, 0);
beginGroup(groupFlags, r);
end;
procedure UIContext.endGroup;
var
newGroup: Group;
parentGroup: PGroup;
maxBoundX: integer;
minBoundX: integer;
maxBoundY: integer;
minBoundY: integer;
begin
// Pop the new group.
newGroup := m_groupStack[m_groupIndex];
Dec(m_groupIndex);
parentGroup := @m_groupStack[m_groupIndex];
// add any increment from the embedded group
if (parentGroup^.flags and (GroupFlags_LayoutVertical or GroupFlags_LayoutHorizontal)) > 0 then
begin
maxBoundX := max(parentGroup^.bounds.x + parentGroup^.bounds.w, newGroup.bounds.x + newGroup.bounds.w + newGroup.margin);
minBoundX := min(parentGroup^.bounds.x, newGroup.bounds.x - newGroup.margin);
parentGroup^.bounds.x := minBoundX;
parentGroup^.bounds.w := maxBoundX - minBoundX;
maxBoundY := max(parentGroup^.bounds.y + parentGroup^.bounds.h, newGroup.bounds.y + newGroup.bounds.h + newGroup.margin);
minBoundY := min(parentGroup^.bounds.y, newGroup.bounds.y - newGroup.margin);
parentGroup^.bounds.y := minBoundY;
parentGroup^.bounds.h := maxBoundY - minBoundY;
end;
{$IFDEF DEBUG} Painter.drawDebugRect(newGroup.bounds); {$ENDIF}
end;
procedure UIContext.beginFrame(groupFlags: integer; const rect: Rect; style: integer);
begin
beginGroup(groupFlags, rect);
end;
procedure UIContext.endFrame;
begin
endGroup;
Painter.drawFrame(m_groupStack[m_groupIndex + 1].bounds, m_groupStack[m_groupIndex + 1].margin, 0);
end;
function UIContext.beginPanel(var r: Rect; const Text: string; var isUnfold: boolean; groupFlags: integer; style: integer): boolean;
var
rt: Rect;
ra: Rect;
rpanel: Rect;
aRect: Rect;
focus: boolean;
hover: boolean;
tmp: Rect;
begin
rpanel := Painter.getPanelRect(SetRect(r.x, r.y), Text, rt, ra);
if (groupFlags and GroupFlags_LayoutDefault) > 0 then
groupFlags := GroupFlags_LayoutDefaultFallback;
beginGroup((groupFlags or GroupFlags_LayoutNoMargin or GroupFlags_LayoutNoSpace) and GroupFlags_StartXMask, rpanel);
aRect := m_groupStack[m_groupIndex].bounds;
focus := hasFocus(aRect);
hover := isHover(aRect);
if focus then
begin
m_uiOnFocus := True;
r.x := r.x + (m_currentCursor.x - m_mouseButton[0].cursor.x);
r.y := r.y + (m_currentCursor.y - m_mouseButton[0].cursor.y);
aRect.x := aRect.x + (m_currentCursor.x - m_mouseButton[0].cursor.x);
aRect.y := aRect.y + (m_currentCursor.y - m_mouseButton[0].cursor.y);
m_mouseButton[0].cursor := m_currentCursor;
end;
if ((m_mouseButton[0].state and ButtonFlags_End) > 0) and focus and (overlap(SetRect(aRect.x + ra.x, aRect.y + ra.y, ra.w, ra.h), m_currentCursor)) then
isUnfold := not isUnfold;
Painter.drawPanel(aRect, Text, rt, ra, isUnfold, hover, focus, style);
if isUnfold then
begin
tmp.Rect(0, 0, r.w, r.h);
beginFrame(groupFlags, tmp);
Result := True;
end
else
begin
endGroup;
Result := False;
end;
end;
procedure UIContext.endPanel;
begin
endFrame;
endGroup;
end;
function UIContext.getGroupWidth: integer;
begin
Result := m_groupStack[m_groupIndex].bounds.w;
end;
function UIContext.getGroupHeight: integer;
begin
Result := m_groupStack[m_groupIndex].bounds.h;
end;
function UIContext.getCursorX: integer;
begin
Result := m_currentCursor.x;
end;
function UIContext.getCursorY: integer;
begin
Result := m_currentCursor.y;
end;
function UIContext.getMouseState(button: integer): ButtonState;
begin
Result := m_mouseButton[button];
end;
procedure UIContext.doTextureView(const aRect: Rect; const texID: integer; zoomRect: Rect; mipLevel: integer; texelScale: double; texelOffset: double; red: integer; green: integer; blue: integer; alpha: integer; style: integer);
var
rt: Rect;
rr: Rect;
begin
rr := placeRect(Painter.getTextureViewRect(aRect, rt));
if (zoomRect.w = 0) or (zoomRect.h = 0) then
zoomRect.Rect(0, 0, rt.w, rt.h);
Painter.drawTextureView(rr, texID, rt, zoomRect, mipLevel, texelScale, texelOffset, red, green, blue, alpha, style);
end;
function UIContext.getPainter: UIPainter;
begin
Result := Painter;
end;
function UIContext.window: Rect;
begin
Result := m_window;
end;
procedure UIContext.setCursor(x: integer; y: integer);
begin
m_currentCursor.x := x;
m_currentCursor.y := m_window.h - y;
end;
function UIContext.overlap(const aRect: Rect; const p: Point): boolean;
begin
Result := (p.x >= aRect.x) and (p.x < aRect.x + aRect.w) and
(p.y >= aRect.y) and (p.y < aRect.y + aRect.h);
end;
function UIContext.hasFocus(const aRect: Rect): boolean;
begin
if m_twoStepFocus then
Result := overlap(aRect, m_focusPoint)
else
Result := ((m_mouseButton[0].state and ButtonFlags_On) > 0) and overlap(aRect, m_mouseButton[0].cursor);
end;
function UIContext.isHover(const aRect: Rect): boolean;
begin
if m_uiOnFocus and not hasFocus(aRect) then
Result := False
else
Result := overlap(aRect, m_currentCursor);
end;
procedure UIContext.SetPainter(AValue: UIPainter);
begin
if FPainter=AValue then
exit;
FPainter:=AValue;
end;
function UIContext.placeRect(const r: Rect): Rect;
var
aGroup: PGroup;
aRect: Rect;
layout: integer;
alignment: integer;
minBoundX: integer;
minBoundY: integer;
begin
aGroup := @m_groupStack[m_groupIndex];
aRect := r;
layout := aGroup^.flags and GroupFlags_LayoutMask;
alignment := aGroup^.flags and GroupFlags_AlignMask;
if layout = GroupFlags_LayoutNone then
begin
// Translate rect to absolute coordinates.
aRect.x := aRect.x + (aGroup^.bounds.x);
aRect.y := aRect.y + (aGroup^.bounds.y);
end
else
if layout = GroupFlags_LayoutVertical then
begin
// Vertical behavior
if (alignment and GroupFlags_AlignTop) > 0 then
begin
// Move down bounds.y with the space for the new rect
aGroup^.bounds.y -= EvalBool(aGroup^.bounds.h > 0) * aGroup^.space + aRect.h;
// Widget's y is the group bounds.y
aRect.y := aGroup^.bounds.y;
end
else
aRect.y := aGroup^.bounds.y + aGroup^.bounds.h + EvalBool(aGroup^.bounds.h > 0) * aGroup^.space;
// Add space after first object inserted in the group
aGroup^.bounds.h += EvalBool(aGroup^.bounds.h > 0) * aGroup^.space + aRect.h;
// Horizontal behavior
if (alignment and GroupFlags_AlignRight) > 0 then
begin
aRect.x := aRect.x + (aGroup^.bounds.x + aGroup^.bounds.w - aRect.w);
minBoundX := min(aGroup^.bounds.x, aRect.x);
aGroup^.bounds.w := aGroup^.bounds.x + aGroup^.bounds.w - minBoundX;
aGroup^.bounds.x := minBoundX;
end
else
begin
aGroup^.bounds.w := max(aGroup^.bounds.w, aRect.x + aRect.w);
aRect.x := aRect.x + (aGroup^.bounds.x);
end;
end
else
if (layout = GroupFlags_LayoutHorizontal) then
begin
// Horizontal behavior
if (alignment and GroupFlags_AlignRight) > 0 then
begin
// Move left bounds.x with the space for the new rect
aGroup^.bounds.x -= EvalBool(aGroup^.bounds.w > 0) * aGroup^.space + aRect.w;
// Widget's x is the group bounds.x
aRect.x := aGroup^.bounds.x;
end
else
aRect.x := aGroup^.bounds.x + aGroup^.bounds.w + EvalBool(aGroup^.bounds.w > 0) * aGroup^.space;
// Add space after first object inserted in the group
aGroup^.bounds.w += EvalBool(aGroup^.bounds.w > 0) * aGroup^.space + aRect.w;
// Vertical behavior
if (alignment and GroupFlags_AlignTop) > 0 then
begin
aRect.y := aRect.y + (aGroup^.bounds.y + aGroup^.bounds.h - aRect.h);
minBoundY := min(aGroup^.bounds.y, aRect.y);
aGroup^.bounds.h := aGroup^.bounds.y + aGroup^.bounds.h - minBoundY;
aGroup^.bounds.y := minBoundY;
end
else
begin
aGroup^.bounds.h := max(aGroup^.bounds.h, aRect.y + aRect.h);
aRect.y := aRect.y + (aGroup^.bounds.y);
end;
end;
Result := aRect;
end;
end.