From 2719aecadb78165e62332cea7cad406fe0d891a5 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Thu, 8 May 2014 08:59:33 +0400 Subject: [PATCH] support underlined text style, and underlining of hotkey chars in text --- examples/example1/src/main.d | 22 +++++++-------- src/dlangui/graphics/fonts.d | 49 +++++++++++++++++++++++++--------- src/dlangui/widgets/controls.d | 4 +-- src/dlangui/widgets/styles.d | 33 +++++++++++++++++++++-- src/dlangui/widgets/widget.d | 15 ++++++++++- 5 files changed, 95 insertions(+), 28 deletions(-) diff --git a/examples/example1/src/main.d b/examples/example1/src/main.d index 8051231d..e8aeaa7a 100644 --- a/examples/example1/src/main.d +++ b/examples/example1/src/main.d @@ -57,25 +57,25 @@ extern (C) int UIAppMain(string[] args) { static if (true) { VerticalLayout contentLayout = new VerticalLayout(); MenuItem mainMenuItems = new MenuItem(); - 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(11, "Save..."d, "document-save", KeyCode.KEY_S, KeyFlag.Control)); + 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(11, "&Save..."d, "document-save", KeyCode.KEY_S, KeyFlag.Control)); 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(101, "File 2"d)); - openRecentItem.add(new Action(102, "File 3"d)); - openRecentItem.add(new Action(103, "File 4"d)); - openRecentItem.add(new Action(104, "File 5"d)); + openRecentItem.add(new Action(100, "&1: File 1"d)); + openRecentItem.add(new Action(101, "&2: File 2"d)); + openRecentItem.add(new Action(102, "&3: File 3"d)); + openRecentItem.add(new Action(103, "&4: File 4"d)); + openRecentItem.add(new Action(104, "&5: File 5"d)); fileItem.add(openRecentItem); - fileItem.add(new Action(12, "Exit"d, "document-close", KeyCode.KEY_X, KeyFlag.Alt)); - MenuItem editItem = new MenuItem(new Action(2, "Edit"d)); + fileItem.add(new Action(12, "E&xit"d, "document-close", KeyCode.KEY_X, KeyFlag.Alt)); + 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.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.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(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)); MenuItem helpItem = new MenuItem(new Action(4, "Help"d)); helpItem.add(new Action(40, "View Help"d)); diff --git a/src/dlangui/graphics/fonts.d b/src/dlangui/graphics/fonts.d index 88c2e63e..c3d12e34 100644 --- a/src/dlangui/graphics/fonts.d +++ b/src/dlangui/graphics/fonts.d @@ -22,6 +22,7 @@ module dlangui.graphics.fonts; public import dlangui.graphics.drawbuf; public import dlangui.core.types; public import dlangui.core.logger; +private import dlangui.widgets.styles; import std.algorithm; /// font family @@ -46,11 +47,11 @@ enum FontWeight : int { Bold = 800 } -immutable dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad; -immutable dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b; -immutable dchar UNICODE_NO_BREAK_SPACE = 0x00a0; -immutable dchar UNICODE_HYPHEN = 0x2010; -immutable dchar UNICODE_NB_HYPHEN = 0x2011; +immutable dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad; +immutable dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b; +immutable dchar UNICODE_NO_BREAK_SPACE = 0x00a0; +immutable dchar UNICODE_HYPHEN = 0x2010; +immutable dchar UNICODE_NB_HYPHEN = 0x2011; version (USE_OPENGL) { @@ -162,6 +163,7 @@ struct GlyphCache } } +immutable int MAX_WIDTH_UNSPECIFIED = int.max; /// Font object class Font : RefCountedObject { @@ -218,7 +220,7 @@ class Font : RefCountedObject { * tabSize = tabulation size, in number of spaces * 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) return 0; const dchar * pstr = text.ptr; @@ -242,7 +244,10 @@ class Font : RefCountedObject { charsMeasured = i + 1; x = tabPosition; 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 //auto measureEnd = std.datetime.Clock.currAppTick; //auto duration = measureEnd - measureStart; @@ -275,10 +280,10 @@ class Font : RefCountedObject { * text = text string to measure * 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) _textSizeBuffer.length = text.length + 1; - int charsMeasured = measureText(text, _textSizeBuffer, maxWidth); + int charsMeasured = measureText(text, _textSizeBuffer, maxWidth, tabSize, tabOffset, textFlags); if (charsMeasured < 1) return Point(0,0); return Point(_textSizeBuffer[charsMeasured - 1], height); @@ -295,26 +300,46 @@ class Font : RefCountedObject { * color = color for drawing of glyphs * tabSize = tabulation size, in number of spaces * 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) return; // nothing to draw - empty text if (_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; if (clip.empty) return; // not visible - clipped out if (y + height < clip.top || y >= clip.bottom) return; // not visible - fully above or below clipping rectangle 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++) { + 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; if (x + xx > clip.right) break; if (x + xx + 255 < clip.left) 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') continue; Glyph * glyph = getCharGlyph(ch); diff --git a/src/dlangui/widgets/controls.d b/src/dlangui/widgets/controls.d index d8d0009b..3d99bf60 100644 --- a/src/dlangui/widgets/controls.d +++ b/src/dlangui/widgets/controls.d @@ -89,7 +89,7 @@ class TextWidget : Widget { override void measure(int parentWidth, int parentHeight) { FontRef font = font(); //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 duration = measureEnd - measureStart; //if (duration.length > 10) @@ -108,7 +108,7 @@ class TextWidget : Widget { FontRef font = font(); Point sz = font.textSize(text); applyAlign(rc, sz); - font.drawText(buf, rc.left, rc.top, text, textColor); + font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags); } } diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 3da7bd08..346d989b 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -36,6 +36,7 @@ immutable ubyte FONT_STYLE_NORMAL = 0x00; immutable ubyte FONT_STYLE_ITALIC = 0x01; /// use as widget.layout() param to avoid applying of parent size immutable int SIZE_UNSPECIFIED = int.max; +immutable uint TEXT_FLAGS_UNSPECIFIED = uint.max; immutable int FILL_PARENT = int.max - 1; immutable int WRAP_CONTENT = int.max - 2; @@ -53,6 +54,16 @@ enum Align : ubyte { 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 { protected string _id; protected string _drawableId; @@ -93,6 +104,7 @@ class Style { protected ushort _fontWeight = FONT_WEIGHT_UNSPECIFIED; protected uint _backgroundColor = COLOR_UNSPECIFIED; protected uint _textColor = COLOR_UNSPECIFIED; + protected uint _textFlags = 0; protected string _fontFace; protected string _backgroundImageId; protected Rect _padding; @@ -260,7 +272,15 @@ class Style { return parentStyle.textColor; } - //=================================================== + /// text flags + @property uint textFlags() const { + if (_textFlags != TEXT_FLAGS_UNSPECIFIED) + return _textFlags; + else + return parentStyle.textFlags; + } + + //=================================================== // background /// background color @@ -428,6 +448,11 @@ class Style { return this; } + @property Style textFlags(uint flags) { + _textFlags = flags; + return this; + } + @property Style backgroundColor(uint color) { _backgroundColor = color; _backgroundImageId = null; @@ -514,6 +539,8 @@ class Style { child._stateMask = stateMask; child._stateValue = stateValue; child._backgroundColor = COLOR_UNSPECIFIED; + child._textColor = COLOR_UNSPECIFIED; + child._textFlags = TEXT_FLAGS_UNSPECIFIED; _substates ~= child; return child; } @@ -573,6 +600,8 @@ class Theme : Style { style._align = Align.Unspecified; // inherit style._padding.left = SIZE_UNSPECIFIED; // inherit style._margins.left = SIZE_UNSPECIFIED; // inherit + style._textColor = COLOR_UNSPECIFIED; // inherit + style._textFlags = TEXT_FLAGS_UNSPECIFIED; // inherit return style; } @@ -725,7 +754,7 @@ Theme createDefaultTheme() { menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa); 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_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); Style transparentButtonBackground = res.createSubstyle("TRANSPARENT_BUTTON_BACKGROUND").backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080) ; diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 634d537a..32342ac9 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -299,7 +299,20 @@ class Widget { invalidate(); return this; } - /// returns font face + /// 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 @property string fontFace() const { return stateStyle.fontFace; } /// set font face for widget - override one from style @property Widget fontFace(string face) {