mirror of https://github.com/buggins/dlangui.git
support underlined text style, and underlining of hotkey chars in text
This commit is contained in:
parent
e0c9b8bc9c
commit
2719aecadb
|
@ -57,25 +57,25 @@ extern (C) int UIAppMain(string[] args) {
|
||||||
static if (true) {
|
static if (true) {
|
||||||
VerticalLayout contentLayout = new VerticalLayout();
|
VerticalLayout contentLayout = new VerticalLayout();
|
||||||
MenuItem mainMenuItems = new MenuItem();
|
MenuItem mainMenuItems = new MenuItem();
|
||||||
MenuItem fileItem = new MenuItem(new Action(1, "File"d));
|
MenuItem fileItem = new MenuItem(new Action(1, "&File"d));
|
||||||
fileItem.add(new Action(10, "Open..."d, "document-open", KeyCode.KEY_O, KeyFlag.Control));
|
fileItem.add(new Action(10, "&Open..."d, "document-open", KeyCode.KEY_O, KeyFlag.Control));
|
||||||
fileItem.add(new Action(11, "Save..."d, "document-save", KeyCode.KEY_S, KeyFlag.Control));
|
fileItem.add(new Action(11, "&Save..."d, "document-save", KeyCode.KEY_S, KeyFlag.Control));
|
||||||
MenuItem openRecentItem = new MenuItem(new Action(13, "Open recent..."d, "document-open-recent"));
|
MenuItem openRecentItem = new MenuItem(new Action(13, "Open recent..."d, "document-open-recent"));
|
||||||
openRecentItem.add(new Action(100, "File 1"d));
|
openRecentItem.add(new Action(100, "&1: File 1"d));
|
||||||
openRecentItem.add(new Action(101, "File 2"d));
|
openRecentItem.add(new Action(101, "&2: File 2"d));
|
||||||
openRecentItem.add(new Action(102, "File 3"d));
|
openRecentItem.add(new Action(102, "&3: File 3"d));
|
||||||
openRecentItem.add(new Action(103, "File 4"d));
|
openRecentItem.add(new Action(103, "&4: File 4"d));
|
||||||
openRecentItem.add(new Action(104, "File 5"d));
|
openRecentItem.add(new Action(104, "&5: File 5"d));
|
||||||
fileItem.add(openRecentItem);
|
fileItem.add(openRecentItem);
|
||||||
fileItem.add(new Action(12, "Exit"d, "document-close", KeyCode.KEY_X, KeyFlag.Alt));
|
fileItem.add(new Action(12, "E&xit"d, "document-close", KeyCode.KEY_X, KeyFlag.Alt));
|
||||||
MenuItem editItem = new MenuItem(new Action(2, "Edit"d));
|
MenuItem editItem = new MenuItem(new Action(2, "&Edit"d));
|
||||||
editItem.add(new Action(EditorActions.Copy, "Copy"d, "edit-copy", KeyCode.KEY_C, KeyFlag.Control));
|
editItem.add(new Action(EditorActions.Copy, "Copy"d, "edit-copy", KeyCode.KEY_C, KeyFlag.Control));
|
||||||
editItem.add(new Action(EditorActions.Paste, "Paste"d, "edit-paste", KeyCode.KEY_V, KeyFlag.Control));
|
editItem.add(new Action(EditorActions.Paste, "Paste"d, "edit-paste", KeyCode.KEY_V, KeyFlag.Control));
|
||||||
editItem.add(new Action(EditorActions.Cut, "Cut"d, "edit-cut", KeyCode.KEY_X, KeyFlag.Control));
|
editItem.add(new Action(EditorActions.Cut, "Cut"d, "edit-cut", KeyCode.KEY_X, KeyFlag.Control));
|
||||||
editItem.add(new Action(EditorActions.Undo, "Undo"d, "edit-undo", KeyCode.KEY_Z, KeyFlag.Control));
|
editItem.add(new Action(EditorActions.Undo, "Undo"d, "edit-undo", KeyCode.KEY_Z, KeyFlag.Control));
|
||||||
editItem.add(new Action(EditorActions.Redo, "Redo"d, "edit-redo", KeyCode.KEY_Y, KeyFlag.Control));
|
editItem.add(new Action(EditorActions.Redo, "Redo"d, "edit-redo", KeyCode.KEY_Y, KeyFlag.Control));
|
||||||
editItem.add(new Action(20, "Preferences..."d));
|
editItem.add(new Action(20, "Preferences..."d));
|
||||||
MenuItem windowItem = new MenuItem(new Action(3, "Window"d));
|
MenuItem windowItem = new MenuItem(new Action(3, "&Window"d));
|
||||||
windowItem.add(new Action(30, "Preferences"d));
|
windowItem.add(new Action(30, "Preferences"d));
|
||||||
MenuItem helpItem = new MenuItem(new Action(4, "Help"d));
|
MenuItem helpItem = new MenuItem(new Action(4, "Help"d));
|
||||||
helpItem.add(new Action(40, "View Help"d));
|
helpItem.add(new Action(40, "View Help"d));
|
||||||
|
|
|
@ -22,6 +22,7 @@ module dlangui.graphics.fonts;
|
||||||
public import dlangui.graphics.drawbuf;
|
public import dlangui.graphics.drawbuf;
|
||||||
public import dlangui.core.types;
|
public import dlangui.core.types;
|
||||||
public import dlangui.core.logger;
|
public import dlangui.core.logger;
|
||||||
|
private import dlangui.widgets.styles;
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
|
|
||||||
/// font family
|
/// font family
|
||||||
|
@ -162,6 +163,7 @@ struct GlyphCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
immutable int MAX_WIDTH_UNSPECIFIED = int.max;
|
||||||
|
|
||||||
/// Font object
|
/// Font object
|
||||||
class Font : RefCountedObject {
|
class Font : RefCountedObject {
|
||||||
|
@ -218,7 +220,7 @@ class Font : RefCountedObject {
|
||||||
* tabSize = tabulation size, in number of spaces
|
* tabSize = tabulation size, in number of spaces
|
||||||
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
|
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
|
||||||
******************************************************************************************/
|
******************************************************************************************/
|
||||||
int measureText(const dchar[] text, ref int[] widths, int maxWidth=int.max, int tabSize = 4, int tabOffset = 0) {
|
int measureText(const dchar[] text, ref int[] widths, int maxWidth=MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
|
||||||
if (text.length == 0)
|
if (text.length == 0)
|
||||||
return 0;
|
return 0;
|
||||||
const dchar * pstr = text.ptr;
|
const dchar * pstr = text.ptr;
|
||||||
|
@ -242,6 +244,9 @@ class Font : RefCountedObject {
|
||||||
charsMeasured = i + 1;
|
charsMeasured = i + 1;
|
||||||
x = tabPosition;
|
x = tabPosition;
|
||||||
continue;
|
continue;
|
||||||
|
} else if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys))) {
|
||||||
|
pwidths[i] = x;
|
||||||
|
continue; // skip '&' in hot key when measuring
|
||||||
}
|
}
|
||||||
Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better
|
Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better
|
||||||
//auto measureEnd = std.datetime.Clock.currAppTick;
|
//auto measureEnd = std.datetime.Clock.currAppTick;
|
||||||
|
@ -275,10 +280,10 @@ class Font : RefCountedObject {
|
||||||
* text = text string to measure
|
* text = text string to measure
|
||||||
* maxWidth = maximum width - measure is stopping if max width is reached
|
* maxWidth = maximum width - measure is stopping if max width is reached
|
||||||
************************************************************************/
|
************************************************************************/
|
||||||
Point textSize(const dchar[] text, int maxWidth = int.max) {
|
Point textSize(const dchar[] text, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
|
||||||
if (_textSizeBuffer.length < text.length + 1)
|
if (_textSizeBuffer.length < text.length + 1)
|
||||||
_textSizeBuffer.length = text.length + 1;
|
_textSizeBuffer.length = text.length + 1;
|
||||||
int charsMeasured = measureText(text, _textSizeBuffer, maxWidth);
|
int charsMeasured = measureText(text, _textSizeBuffer, maxWidth, tabSize, tabOffset, textFlags);
|
||||||
if (charsMeasured < 1)
|
if (charsMeasured < 1)
|
||||||
return Point(0,0);
|
return Point(0,0);
|
||||||
return Point(_textSizeBuffer[charsMeasured - 1], height);
|
return Point(_textSizeBuffer[charsMeasured - 1], height);
|
||||||
|
@ -295,26 +300,46 @@ class Font : RefCountedObject {
|
||||||
* color = color for drawing of glyphs
|
* color = color for drawing of glyphs
|
||||||
* tabSize = tabulation size, in number of spaces
|
* tabSize = tabulation size, in number of spaces
|
||||||
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
|
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
|
||||||
|
* textFlags = set of TextFlag bit fields
|
||||||
****************************************************************************************/
|
****************************************************************************************/
|
||||||
void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0) {
|
void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
|
||||||
if (text.length == 0)
|
if (text.length == 0)
|
||||||
return; // nothing to draw - empty text
|
return; // nothing to draw - empty text
|
||||||
if (_textSizeBuffer.length < text.length)
|
if (_textSizeBuffer.length < text.length)
|
||||||
_textSizeBuffer.length = text.length;
|
_textSizeBuffer.length = text.length;
|
||||||
int charsMeasured = measureText(text, _textSizeBuffer, int.max, tabSize, tabOffset);
|
int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
|
||||||
Rect clip = buf.clipOrFullRect;
|
Rect clip = buf.clipOrFullRect;
|
||||||
if (clip.empty)
|
if (clip.empty)
|
||||||
return; // not visible - clipped out
|
return; // not visible - clipped out
|
||||||
if (y + height < clip.top || y >= clip.bottom)
|
if (y + height < clip.top || y >= clip.bottom)
|
||||||
return; // not visible - fully above or below clipping rectangle
|
return; // not visible - fully above or below clipping rectangle
|
||||||
int _baseline = baseline;
|
int _baseline = baseline;
|
||||||
|
bool underline = (textFlags & TextFlag.Underline) != 0;
|
||||||
|
int underlineHeight = 1;
|
||||||
|
int underlineY = y + _baseline + underlineHeight * 2;
|
||||||
for (int i = 0; i < charsMeasured; i++) {
|
for (int i = 0; i < charsMeasured; i++) {
|
||||||
|
dchar ch = text[i];
|
||||||
|
if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys))) {
|
||||||
|
if (textFlags & TextFlag.UnderlineHotKeys)
|
||||||
|
underline = true; // turn ON underline for hot key
|
||||||
|
continue; // skip '&' in hot key when measuring
|
||||||
|
}
|
||||||
int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
|
int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
|
||||||
if (x + xx > clip.right)
|
if (x + xx > clip.right)
|
||||||
break;
|
break;
|
||||||
if (x + xx + 255 < clip.left)
|
if (x + xx + 255 < clip.left)
|
||||||
continue; // far at left of clipping region
|
continue; // far at left of clipping region
|
||||||
dchar ch = text[i];
|
|
||||||
|
if (underline) {
|
||||||
|
int xx2 = _textSizeBuffer[i];
|
||||||
|
// draw underline
|
||||||
|
if (xx2 > xx)
|
||||||
|
buf.fillRect(Rect(x + xx, underlineY, x + xx2, underlineY + underlineHeight), color);
|
||||||
|
// turn off underline after hot key
|
||||||
|
if (!(textFlags & TextFlag.Underline))
|
||||||
|
underline = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (ch == ' ' || ch == '\t')
|
if (ch == ' ' || ch == '\t')
|
||||||
continue;
|
continue;
|
||||||
Glyph * glyph = getCharGlyph(ch);
|
Glyph * glyph = getCharGlyph(ch);
|
||||||
|
|
|
@ -89,7 +89,7 @@ class TextWidget : Widget {
|
||||||
override void measure(int parentWidth, int parentHeight) {
|
override void measure(int parentWidth, int parentHeight) {
|
||||||
FontRef font = font();
|
FontRef font = font();
|
||||||
//auto measureStart = std.datetime.Clock.currAppTick;
|
//auto measureStart = std.datetime.Clock.currAppTick;
|
||||||
Point sz = font.textSize(text);
|
Point sz = font.textSize(text, MAX_WIDTH_UNSPECIFIED, 4, 0, textFlags);
|
||||||
//auto measureEnd = std.datetime.Clock.currAppTick;
|
//auto measureEnd = std.datetime.Clock.currAppTick;
|
||||||
//auto duration = measureEnd - measureStart;
|
//auto duration = measureEnd - measureStart;
|
||||||
//if (duration.length > 10)
|
//if (duration.length > 10)
|
||||||
|
@ -108,7 +108,7 @@ class TextWidget : Widget {
|
||||||
FontRef font = font();
|
FontRef font = font();
|
||||||
Point sz = font.textSize(text);
|
Point sz = font.textSize(text);
|
||||||
applyAlign(rc, sz);
|
applyAlign(rc, sz);
|
||||||
font.drawText(buf, rc.left, rc.top, text, textColor);
|
font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ immutable ubyte FONT_STYLE_NORMAL = 0x00;
|
||||||
immutable ubyte FONT_STYLE_ITALIC = 0x01;
|
immutable ubyte FONT_STYLE_ITALIC = 0x01;
|
||||||
/// use as widget.layout() param to avoid applying of parent size
|
/// use as widget.layout() param to avoid applying of parent size
|
||||||
immutable int SIZE_UNSPECIFIED = int.max;
|
immutable int SIZE_UNSPECIFIED = int.max;
|
||||||
|
immutable uint TEXT_FLAGS_UNSPECIFIED = uint.max;
|
||||||
|
|
||||||
immutable int FILL_PARENT = int.max - 1;
|
immutable int FILL_PARENT = int.max - 1;
|
||||||
immutable int WRAP_CONTENT = int.max - 2;
|
immutable int WRAP_CONTENT = int.max - 2;
|
||||||
|
@ -53,6 +54,16 @@ enum Align : ubyte {
|
||||||
TopLeft = Left | Top,
|
TopLeft = Left | Top,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// text drawing flag bits
|
||||||
|
enum TextFlag : uint {
|
||||||
|
/// text contains hot key prefixed with & char (e.g. "&File")
|
||||||
|
HotKeys = 1,
|
||||||
|
/// underline hot key when drawing
|
||||||
|
UnderlineHotKeys = 2,
|
||||||
|
/// underline text when drawing
|
||||||
|
Underline = 4
|
||||||
|
}
|
||||||
|
|
||||||
class DrawableAttribute {
|
class DrawableAttribute {
|
||||||
protected string _id;
|
protected string _id;
|
||||||
protected string _drawableId;
|
protected string _drawableId;
|
||||||
|
@ -93,6 +104,7 @@ class Style {
|
||||||
protected ushort _fontWeight = FONT_WEIGHT_UNSPECIFIED;
|
protected ushort _fontWeight = FONT_WEIGHT_UNSPECIFIED;
|
||||||
protected uint _backgroundColor = COLOR_UNSPECIFIED;
|
protected uint _backgroundColor = COLOR_UNSPECIFIED;
|
||||||
protected uint _textColor = COLOR_UNSPECIFIED;
|
protected uint _textColor = COLOR_UNSPECIFIED;
|
||||||
|
protected uint _textFlags = 0;
|
||||||
protected string _fontFace;
|
protected string _fontFace;
|
||||||
protected string _backgroundImageId;
|
protected string _backgroundImageId;
|
||||||
protected Rect _padding;
|
protected Rect _padding;
|
||||||
|
@ -260,6 +272,14 @@ class Style {
|
||||||
return parentStyle.textColor;
|
return parentStyle.textColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// text flags
|
||||||
|
@property uint textFlags() const {
|
||||||
|
if (_textFlags != TEXT_FLAGS_UNSPECIFIED)
|
||||||
|
return _textFlags;
|
||||||
|
else
|
||||||
|
return parentStyle.textFlags;
|
||||||
|
}
|
||||||
|
|
||||||
//===================================================
|
//===================================================
|
||||||
// background
|
// background
|
||||||
|
|
||||||
|
@ -428,6 +448,11 @@ class Style {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property Style textFlags(uint flags) {
|
||||||
|
_textFlags = flags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@property Style backgroundColor(uint color) {
|
@property Style backgroundColor(uint color) {
|
||||||
_backgroundColor = color;
|
_backgroundColor = color;
|
||||||
_backgroundImageId = null;
|
_backgroundImageId = null;
|
||||||
|
@ -514,6 +539,8 @@ class Style {
|
||||||
child._stateMask = stateMask;
|
child._stateMask = stateMask;
|
||||||
child._stateValue = stateValue;
|
child._stateValue = stateValue;
|
||||||
child._backgroundColor = COLOR_UNSPECIFIED;
|
child._backgroundColor = COLOR_UNSPECIFIED;
|
||||||
|
child._textColor = COLOR_UNSPECIFIED;
|
||||||
|
child._textFlags = TEXT_FLAGS_UNSPECIFIED;
|
||||||
_substates ~= child;
|
_substates ~= child;
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
@ -573,6 +600,8 @@ class Theme : Style {
|
||||||
style._align = Align.Unspecified; // inherit
|
style._align = Align.Unspecified; // inherit
|
||||||
style._padding.left = SIZE_UNSPECIFIED; // inherit
|
style._padding.left = SIZE_UNSPECIFIED; // inherit
|
||||||
style._margins.left = SIZE_UNSPECIFIED; // inherit
|
style._margins.left = SIZE_UNSPECIFIED; // inherit
|
||||||
|
style._textColor = COLOR_UNSPECIFIED; // inherit
|
||||||
|
style._textFlags = TEXT_FLAGS_UNSPECIFIED; // inherit
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -725,7 +754,7 @@ Theme createDefaultTheme() {
|
||||||
menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
|
menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
|
||||||
menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
|
menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
|
||||||
res.createSubstyle("MENU_ICON").setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left);
|
res.createSubstyle("MENU_ICON").setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left);
|
||||||
res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left);
|
res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys);
|
||||||
res.createSubstyle("MENU_ACCEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left);
|
res.createSubstyle("MENU_ACCEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left);
|
||||||
|
|
||||||
Style transparentButtonBackground = res.createSubstyle("TRANSPARENT_BUTTON_BACKGROUND").backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080) ;
|
Style transparentButtonBackground = res.createSubstyle("TRANSPARENT_BUTTON_BACKGROUND").backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080) ;
|
||||||
|
|
|
@ -298,6 +298,19 @@ class Widget {
|
||||||
ownStyle.textColor = value;
|
ownStyle.textColor = value;
|
||||||
invalidate();
|
invalidate();
|
||||||
return this;
|
return this;
|
||||||
|
}
|
||||||
|
/// get text flags (bit set of TextFlag enum values)
|
||||||
|
@property uint textFlags() const { return stateStyle.textFlags; }
|
||||||
|
/// set text flags (bit set of TextFlag enum values)
|
||||||
|
@property Widget textFlags(uint value) {
|
||||||
|
ownStyle.textFlags = value;
|
||||||
|
bool oldHotkeys = (ownStyle.textFlags & (TextFlag.HotKeys | TextFlag.UnderlineHotKeys)) != 0;
|
||||||
|
bool newHotkeys = (value & (TextFlag.HotKeys | TextFlag.UnderlineHotKeys)) != 0;
|
||||||
|
if (oldHotkeys != newHotkeys)
|
||||||
|
requestLayout();
|
||||||
|
else
|
||||||
|
invalidate();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
/// returns font face
|
/// returns font face
|
||||||
@property string fontFace() const { return stateStyle.fontFace; }
|
@property string fontFace() const { return stateStyle.fontFace; }
|
||||||
|
|
Loading…
Reference in New Issue