Files
lazarus-ccr/components/nvidia-widgets/src/nvcontext.pas
blaszijk d0340d13eb fixed images in documentation
removed footer date for the time being (to prevent too much changes in documentation)
implemented cleaning of GLUT bitmap font lists
removed unused function in uicontext class
renamed chm file
added missing documentation back in nvcontext.xml

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@2254 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2012-01-12 15:15:59 +00:00

1103 lines
34 KiB
ObjectPascal

//
// 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;
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 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;
implementation
uses
Math;
{ 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.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.