support underlined text style, and underlining of hotkey chars in text

This commit is contained in:
Vadim Lopatin 2014-05-08 08:59:33 +04:00
parent e0c9b8bc9c
commit 2719aecadb
5 changed files with 95 additions and 28 deletions

View File

@ -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));

View File

@ -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);

View File

@ -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);
} }
} }

View File

@ -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) ;

View File

@ -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; }