From 8cd17ce50eecfbfe8086ff3485be4e2d8d1e4564 Mon Sep 17 00:00:00 2001 From: skyjake Date: Fri, 23 Aug 2013 13:44:18 +0300 Subject: [PATCH 01/14] Default Style: Added a HiDPI version of the on/off toggle graphic --- .../graphics/toggle-onoff@2x.png | Bin 0 -> 4437 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doomsday/client/data/defaultstyle.pack/graphics/toggle-onoff@2x.png diff --git a/doomsday/client/data/defaultstyle.pack/graphics/toggle-onoff@2x.png b/doomsday/client/data/defaultstyle.pack/graphics/toggle-onoff@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..902c495e994de8cc2df5ba17c046a31a5d8d0f88 GIT binary patch literal 4437 zcmb_ec|4SD+a64|N{W!gBu0rD`#yHYmL*FwmY88M7^5+^%9%Qvd0dVAjQ`~G?F-|yaz^Ej^ax{mX_mvBRU_)$hqMgRbCR9j2^GUY5o z*+KNQlp~y`W*PusRKcpL8EUJkfeZ?gZ1#OAap;&DzjlrgOR$%#88wo z!&0H-=i$`7R^-UwR7L-w!;)`*HNbE;2-rwOPz6Y2B3Kva8Z1w+bTn^Jb1+jeA*kv{ z?d^f^O?B#?T@oxMB}c2JbbS#LU{~{AsLl}V-nZ``$1APIM+;!o=#^6N1G7(1-!(3M zc}i>__-ZZjf>@}i7+WSoRsru=bFs_%)5(i9?a4{t@k~x+=cyMG529)ehg11Sv!j5S zmdX4d9kOrIVMr$0sep-%^WEK>_w@iGtSQ#7r@)ym;bI!BrxwFksR8tNn`K`Rm?F1i z9UlO^pE!(10K>J>PmcE|V<)ED?6+4+jknOE7w6xn67~5$q!Vb9w0|t<_n-CHo!WcdD@xM+C)=8 z8ID$YVzj_u2?SU@1SlK6|Cu|~^JV(U^Gh^og$e>(@t^lE`*@8_fG-^|IK1wKoaf&!}f#37IxEn!d{r z9PU^Xq7aCPh1f9$`}dCeb4Ur$o$^a}W_9$i2Ka`^8dhr4wc%j^wR1Flu1eRbedjEg zMH$;_UpN-V6}!iXUH>|@&2!i?!gW+nh1Ym;{YZ=*fj>*=RQ5=)O9Sa+4j&1CD>H^R=!#Gc2V&`imYGjxspQ47Jvx@6+Q)p_oZd%G#ZZQo=A~MAEOWf_ z)*EmRhv2QeOf^H;+BqU;HNDlX?T2)4KoYcjRKCH-lc~E=M+X3Z1q_zRbJ4>aJ3gv= zpS(}UUjKenr;<&PsiKj6^z<@7D8#xkfnK?do~BC^L4SHhT{RZLM|J$Ha~$s@>RgS~ zSf)p(tD?2yu8q^shGn9XIoMiGjfEkjM$u-dKD3TVZ?bK&nI8M3ls&U};8xIMbnmk# z2!R46PX?*uY0<*XW;QW479~l~Xa-R8@?1=<^`|~@x%pLcSc^%^l3!e!yC;b%tUhwfo z38TOq10ruIvrn@n`ogn211c}Hxxo4ftlFV`?3ej2q?84kXgl#*fni|M*@SjEJ2-&@ zseIL-h%W$CnkU!Uw&QX4fWHy8(ImzZU0j+QmT zLbNqfvl7v_(W&Tg^sP^lFOB$L#0SP#oG{2$o+)|D|M)DYYWHKGx0Pj=%H-Z*-ie%n zOF!D_PMCSQ#xqS?W5DoZEGEuRa7}Pdl)k!5+bCkQDSr9Xds%za^Kf|vJt9tECzR8m(@rlWKbriM?Dc@0 zZY{Atz8f}MQXPK1a@*-k(=2Q@fisn}mh%*+DQA*Ii{z#JDW(Aw1%9_h|D_F161~??YX?T{4T+4^KAJR z8QZ_EMXV@}S%6Ez)3xKZquVbT5A+P1*i@6t$VZ$f9l;X$k~OVaB_EK;0?odMvP}wY zH?w`xwk{rqMIs{8*@oDxg%*U2h3ABLkQb1L5V*40lI^B<&8f~-w!W>QPGh#Eolk}) z%L{XbM?C8meC1QEN`}~K^lQlLOzXTm;2lvqSeSPhiD_ei=y|>-*thri-1KN==StKV zoxlx&XWiOQN79j<7dnr2W*kdn^tf*?RYB!W1X@@YUN%#aH646j#J=pqHGMo+`>IhAT!Z zvebIk=KDf7QG3*Tp4&s8`R8{FXC`A>{vix6?k zd!_U52;~#G9ps`|>jvaCLGIH$XGWO!!&w@0C-jQetgE~xfc*(IVLS0tEf=SvSFAQM zyK%sz;OD_z>PgL3;kMdE+9$Qgs0~!gKYBd3UJV@2y&HQs`+|6GSMI;C!DX9e-KzYk zh?0v|;xGXcv`HdcM6LQ2ws+B#B}=(0PVc-s(!M77&R4xJT2Xvy{I`&YOP!t%JHRm+ zuZ&(FpIjSH9e3?G)Bd@A$4}B;(#yl4*7IxVZiD2aGgDzmUypWXNtcBo`_e+E_es20 zyS|`1K1F;o;QtUmH-hxNvNiO2J$Qlic>VE?*GEr!uMa~r)t?b5XynzwH$@+<3MXE# zX02A#T$!&mLs?w2Ifjj~izyS!c}~(Pc7pdloqaSL$QhG5Nt(S;v5@|*^?iTc2+qHo zDw^gcogz0Y*9-r)4MXQb72HeIhtYy2@?6$zb)AF1wZHm;)=C^19s+;d+8E=}E8o*E zog;l$sxdmg<=NP^R{Y+_FKRbf@@EXiv#U0NGG}F1KDBQ0EIl})Fz6HEn>Qn{m$ClW z8}kVB%)SqO;eS1n!rsukF)|a-oqneCjIdJL{>`wH zi!S%rRi)S0W%eBRZ0E<^18{eyXpKC6951XV@(i5#3emBTRaYevcsb>5O16tg#iF4 zc*{{PaTp>B7%%TZqMzIMc@nYqzcRVH|5z4ffzX2pR1_it z{W&&8s&IhHUB`r zU+g}J{L}6ORL&6Vjd4M!V{sVQ@8(GuUlxAlFCtKnVn1YH)4p81V zii{NH-|sF(MrlT!P5^-Fjkdaq34&n(DX-tGd}IhT{m`0|sf6DlAoQNR8#iB0>_^)B z0VGu}>3q=R9x>J?cd zdK@LUsc2lzhb_4iWzPGFI9%e!F|{^6{K(G|PGTo9r{|-O@*Em4NnnaReYqgb!f(}4 z*z(T4X2Oeb7)!YfMcbfnF0+|lz)m$G^3@wCaGDEnN}$uy(_N-j%9x| zTtlqg>I_cT-L+cNU2Cha-gX*L$*x6xkd)ZWE4kXF?2Ir?wa{0Y1W=9 literal 0 HcmV?d00001 From cf45557f9027f1b49f3017967957b19b68980541 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 10:26:21 +0300 Subject: [PATCH 02/14] Default Style: Adjusted the "textaccent" color --- doomsday/client/data/defaultstyle.pack/colors.dei | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doomsday/client/data/defaultstyle.pack/colors.dei b/doomsday/client/data/defaultstyle.pack/colors.dei index a4da78cf45..623cdea519 100644 --- a/doomsday/client/data/defaultstyle.pack/colors.dei +++ b/doomsday/client/data/defaultstyle.pack/colors.dei @@ -11,7 +11,7 @@ color background { rgb <0.0, 0.0, 0.0, 0.75> } color accent { rgb <1.0, 0.8, 0.4> } color glow { rgb <1.0, 1.0, 1.0, 0.14> } -color textaccent { rgb $= gui.colorMix(text.rgb, accent.rgb, 0.5) } +color textaccent { rgb $= gui.colorMix(text.rgb, accent.rgb, 0.6) } group inverted { color text { rgb <0.0, 0.0, 0.0> } From 8f3e97b93cb59fd9871f6fd3daef3b6aeb001086 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 11:38:41 +0300 Subject: [PATCH 03/14] Refactor|libgui|Client: Untabbed lines among tabbed lines It is now possible to set the tab stop to -1 to indicate that the line should not be tabbed at all. The objective is to allow reseting the tab stops within a longer piece of content, continuing with a new set of stops. Refactored FontLineWrapping and GLTextComposer to allow processing tabbed lines in distinct ranges. --- .../src/ui/widgets/fontlinewrapping.cpp | 165 +++++++++++------- .../client/src/ui/widgets/gltextcomposer.cpp | 130 +++++++------- doomsday/libgui/include/de/gui/font.h | 1 + doomsday/libgui/src/font.cpp | 9 +- 4 files changed, 176 insertions(+), 129 deletions(-) diff --git a/doomsday/client/src/ui/widgets/fontlinewrapping.cpp b/doomsday/client/src/ui/widgets/fontlinewrapping.cpp index 8275f63770..8b10c904b5 100644 --- a/doomsday/client/src/ui/widgets/fontlinewrapping.cpp +++ b/doomsday/client/src/ui/widgets/fontlinewrapping.cpp @@ -47,6 +47,17 @@ DENG2_PIMPL_NOREF(FontLineWrapping) { info.indent = leftIndent; } + + /* + /// Tab stops are disabled if there is a tab stop < 0 anywhere on the line. + bool tabsDisabled() const + { + for(int i = 0; i < info.segs.size(); ++i) + { + if(info.segs[i].tabStop < 0) return true; + } + return false; + }*/ }; typedef QList Lines; @@ -212,7 +223,7 @@ DENG2_PIMPL_NOREF(FontLineWrapping) return false; } -#if 0 +#if 0 // old algorithm disabled -- now using a character-based maxWrap int findMaxWrapWithStep(int const stepSize, int const begin, int end, int const availableWidth, int *wrapPosMax) @@ -416,6 +427,92 @@ DENG2_PIMPL_NOREF(FontLineWrapping) return wrappedLines; } + + /** + * Wraps a range of lines that contains tab stops. Wrapping takes into + * account the space available for each tab stop. + * + * @param lineRange Range of lines to wrap. + * + * @return End of the range, taking into account possible extra lines produced + * when wrapping long lines. + */ + int wrapLinesWithTabs(Rangei const &lineRange) + { + int extraLinesProduced = 0; + + // Determine the actual positions of each tab stop according to segment widths. + QMap stopMaxWidths; // stop => maxWidth + + for(int i = lineRange.start; i < lineRange.end; ++i) + { + Line *line = lines[i]; + for(int k = 0; k < line->info.segs.size(); ++k) + { + LineInfo::Segment const &seg = line->info.segs[k]; + if(seg.tabStop < 0) continue; + int sw = seg.width; + + // Include overall indent into the first segment width. + if(!k) sw += line->info.indent; + + stopMaxWidths[seg.tabStop] = de::max(stopMaxWidths[seg.tabStop], sw); + } + } + + // Now we can wrap the lines that area too long. + for(int i = lineRange.start; i < lineRange.end + extraLinesProduced; ++i) + { + Line *line = lines[i]; + int curLeft = 0; + int prevRight = 0; + + for(int k = 0; k < line->info.segs.size(); ++k) + { + LineInfo::Segment const &seg = line->info.segs[k]; + int const tab = seg.tabStop; + int const stopWidth = (tab >= 0? stopMaxWidths[tab] : seg.width); + + if(curLeft + stopWidth >= maxWidth) + { + // Wrap the line starting from this segment. + + // The maximum width of the first line is reduced by the + // added amount of tab space: the difference between the + // left edge of the current segment and the right edge of + // the previous one. The maximum widths of subsequent lines + // is also adjusted, so that the available space depends on + // where the current tab is located (indent is added + // because wrapRange automatically subtracts it). + + Lines wrapped = wrapRange(line->line.range, + maxWidth - (curLeft - prevRight), + maxWidth - curLeft + line->info.indent, + line->info.indent); + + extraLinesProduced += wrapped.size() - 1; + + // Replace the original line with these wrapped lines. + delete lines.takeAt(i); + foreach(Line *wl, wrapped) + { + lines.insert(i++, wl); + } + --i; + break; // Proceed to next line. + } + + // Update the coordinate of the previous segment's right edge. + prevRight = curLeft + seg.width; + if(!k) prevRight += line->info.indent; + + // Move on to the next segment's left edge. + curLeft += stopWidth; + } + } + + return lineRange.end + extraLinesProduced; + } }; FontLineWrapping::FontLineWrapping() : d(new Instance) @@ -495,71 +592,7 @@ void FontLineWrapping::wrapTextToWidth(String const &text, Font::RichFormat cons pos = wholeLine.end + 1; } - // Determine the actual positions of each tab stop according to segment widths. - QMap stopMaxWidths; // stop => maxWidth - - for(int i = 0; i < d->lines.size(); ++i) - { - Instance::Line *line = d->lines[i]; - for(int k = 0; k < line->info.segs.size(); ++k) - { - LineInfo::Segment const &seg = line->info.segs[k]; - int sw = seg.width; - - // Include overall indent into the first segment width. - if(!k) sw += line->info.indent; - - stopMaxWidths[seg.tabStop] = de::max(stopMaxWidths[seg.tabStop], sw); - } - } - - // Now we can wrap the lines that area too long. - for(int i = 0; i < d->lines.size(); ++i) - { - Instance::Line *line = d->lines[i]; - int curLeft = 0; - int prevRight = 0; - for(int k = 0; k < line->info.segs.size(); ++k) - { - LineInfo::Segment const &seg = line->info.segs[k]; - int const tab = seg.tabStop; - int const stopWidth = stopMaxWidths[tab]; - - if(curLeft + stopWidth >= maxWidth) - { - // Wrap the line starting from this segment. - - // The maximum width of the first line is reduced by the - // added amount of tab space: the difference between the - // left edge of the current segment and the right edge of - // the previous one. The maximum widths of subsequent lines - // is also adjusted, so that the available space depends on - // where the current tab is located (indent is added - // because wrapRange automatically subtracts it). - - Instance::Lines wrapped = d->wrapRange(line->line.range, - maxWidth - (curLeft - prevRight), - maxWidth - curLeft + line->info.indent, - line->info.indent); - - // Replace the original line with these wrapped lines. - delete d->lines.takeAt(i); - foreach(Instance::Line *wl, wrapped) - { - d->lines.insert(i++, wl); - } - --i; - break; // Proceed to next line. - } - - // Update the coordinate of the previous segment's right edge. - prevRight = curLeft + seg.width; - if(!k) prevRight += line->info.indent; - - // Move on to the next segment's left edge. - curLeft += stopWidth; - } - } + d->wrapLinesWithTabs(Rangei(0, d->lines.size())); } else { diff --git a/doomsday/client/src/ui/widgets/gltextcomposer.cpp b/doomsday/client/src/ui/widgets/gltextcomposer.cpp index 5f28e7d6de..683be77d10 100644 --- a/doomsday/client/src/ui/widgets/gltextcomposer.cpp +++ b/doomsday/client/src/ui/widgets/gltextcomposer.cpp @@ -208,6 +208,72 @@ DENG2_PIMPL(GLTextComposer) return changed; } + + void updateLineLayout(Rangei const &lineRange) + { + // Find the highest tab in use and initialize seg widths. + int highestTab = 0; + for(int i = lineRange.start; i < lineRange.end; ++i) + { + highestTab = de::max(highestTab, wraps->lineInfo(i).highestTabStop()); + + // Initialize the segments with indentation. + for(int k = 0; k < lines[i].segs.size(); ++k) + { + lines[i].segs[k].width = wraps->lineInfo(i).segs[k].width; + } + } + + // Set segment X coordinates by stacking them left-to-right on each line. + for(int i = lineRange.start; i < lineRange.end; ++i) + { + if(lines[i].segs.isEmpty()) continue; + + lines[i].segs[0].x = wraps->lineInfo(i).indent; + + for(int k = 1; k < lines[i].segs.size(); ++k) + { + Instance::Line::Segment &seg = lines[i].segs[k]; + seg.x = lines[i].segs[k - 1].right(); + } + } + + // Align each tab stop with other matching stops on the other lines. + for(int tab = 1; tab <= highestTab; ++tab) + { + int maxRight = 0; + + // Find the maximum right edge for this spot. + for(int i = lineRange.start; i < lineRange.end; ++i) + { + FontLineWrapping::LineInfo const &info = wraps->lineInfo(i); + for(int k = 0; k < info.segs.size(); ++k) + { + Instance::Line::Segment &seg = lines[i].segs[k]; + if(info.segs[k].tabStop >= 0 && info.segs[k].tabStop < tab) + { + maxRight = de::max(maxRight, seg.right()); + } + } + } + + // Move the segments to this position. + for(int i = lineRange.start; i < lineRange.end; ++i) + { + int localRight = maxRight; + + FontLineWrapping::LineInfo const &info = wraps->lineInfo(i); + for(int k = 0; k < info.segs.size(); ++k) + { + if(info.segs[k].tabStop == tab) + { + lines[i].segs[k].x = localRight; + localRight += info.segs[k].width; + } + } + } + } + } }; GLTextComposer::GLTextComposer() : d(new Instance(this)) @@ -316,67 +382,7 @@ void GLTextComposer::makeVertices(Vertices &triStrip, DENG2_ASSERT(d->wraps->height() == d->lines.size()); // Align segments based on tab stops. - int highestTab = 0; - for(int i = 0; i < d->lines.size(); ++i) - { - highestTab = de::max(highestTab, d->wraps->lineInfo(i).highestTabStop()); - - // Initialize the segments with indentation. - for(int k = 0; k < d->lines[i].segs.size(); ++k) - { - // Determine the width of this segment. - d->lines[i].segs[k].width = d->wraps->lineInfo(i).segs[k].width; - } - } - - for(int i = 0; i < d->lines.size(); ++i) - { - if(d->lines[i].segs.isEmpty()) continue; - - d->lines[i].segs[0].x = d->wraps->lineInfo(i).indent; - - for(int k = 1; k < d->lines[i].segs.size(); ++k) - { - Instance::Line::Segment &seg = d->lines[i].segs[k]; - seg.x = d->lines[i].segs[k - 1].right(); - } - } - - // Align each tab stop with other matching stops on the other lines. - for(int tab = 1; tab <= highestTab; ++tab) - { - int maxRight = 0; - - // Find the maximum right edge for this spot. - for(int i = 0; i < d->lines.size(); ++i) - { - FontLineWrapping::LineInfo const &info = d->wraps->lineInfo(i); - for(int k = 0; k < info.segs.size(); ++k) - { - Instance::Line::Segment &seg = d->lines[i].segs[k]; - if(info.segs[k].tabStop < tab) - { - maxRight = de::max(maxRight, seg.right()); - } - } - } - - // Move the segments to this position. - for(int i = 0; i < d->lines.size(); ++i) - { - int localRight = maxRight; - - FontLineWrapping::LineInfo const &info = d->wraps->lineInfo(i); - for(int k = 0; k < info.segs.size(); ++k) - { - if(info.segs[k].tabStop == tab) - { - d->lines[i].segs[k].x = localRight; - localRight += info.segs[k].width; - } - } - } - } + d->updateLineLayout(Rangei(0, d->lines.size())); // Compress lines to fit into the maximum allowed width. for(int i = 0; i < d->lines.size(); ++i) @@ -384,8 +390,10 @@ void GLTextComposer::makeVertices(Vertices &triStrip, Instance::Line &line = d->lines[i]; if(!d->isLineVisible(i) || line.segs.isEmpty()) continue; + /* if(!d->wraps->lineInfo(i).segs.last().tabStop) continue; + */ #ifdef MACOSX # define COMPRESSION_THRESHOLD 1 diff --git a/doomsday/libgui/include/de/gui/font.h b/doomsday/libgui/include/de/gui/font.h index 9f92589563..603f39225c 100644 --- a/doomsday/libgui/include/de/gui/font.h +++ b/doomsday/libgui/include/de/gui/font.h @@ -243,6 +243,7 @@ class LIBGUI_PUBLIC Font bool markIndent() const; bool resetIndent() const; int tabStop() const; + bool isTabless() const; ///< Tabstop < 0. }; private: diff --git a/doomsday/libgui/src/font.cpp b/doomsday/libgui/src/font.cpp index be7ffec504..4da092d01d 100644 --- a/doomsday/libgui/src/font.cpp +++ b/doomsday/libgui/src/font.cpp @@ -48,7 +48,7 @@ DENG2_OBSERVES(EscapeParser, EscapeSequence) Format() : sizeFactor(1.f), weight(OriginalWeight), style(OriginalStyle), colorIndex(-1), markIndent(false), resetIndent(false), - tabStop(0) {} + tabStop(-1 /* untabbed */) {} }; struct FormatRange @@ -139,7 +139,7 @@ DENG2_OBSERVES(EscapeParser, EscapeSequence) break; case 'T': - stack.last().tabStop = de::max(0, code[1].toLatin1() - 'a'); + stack.last().tabStop = de::max(-1, code[1].toLatin1() - 'a'); break; case 'b': @@ -480,6 +480,11 @@ int Font::RichFormat::Iterator::tabStop() const return REF_RANGE_AT(index).format.tabStop; } +bool Font::RichFormat::Iterator::isTabless() const +{ + return tabStop() < 0; +} + #undef REF_RANGE_AT DENG2_PIMPL(Font) From 50a28d7b3feb7f443d856b162b929b99bd01aefb Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 12:08:17 +0300 Subject: [PATCH 04/14] Client|libgui: Untabbed lines will reset tab stops for following lines Now inside a longer styled text content, tab stops are only processed for the tabbed portion of text. The other lines are considered untabbed and the used tab alignment will reset after any untabbed line is encountered. The tab reset escape is _E(`). --- .../src/ui/widgets/fontlinewrapping.cpp | 27 +++++++++-- .../client/src/ui/widgets/gltextcomposer.cpp | 46 +++++++++++++++++-- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/doomsday/client/src/ui/widgets/fontlinewrapping.cpp b/doomsday/client/src/ui/widgets/fontlinewrapping.cpp index 8b10c904b5..01c121ebd0 100644 --- a/doomsday/client/src/ui/widgets/fontlinewrapping.cpp +++ b/doomsday/client/src/ui/widgets/fontlinewrapping.cpp @@ -48,7 +48,6 @@ DENG2_PIMPL_NOREF(FontLineWrapping) info.indent = leftIndent; } - /* /// Tab stops are disabled if there is a tab stop < 0 anywhere on the line. bool tabsDisabled() const { @@ -57,7 +56,7 @@ DENG2_PIMPL_NOREF(FontLineWrapping) if(info.segs[i].tabStop < 0) return true; } return false; - }*/ + } }; typedef QList Lines; @@ -428,6 +427,15 @@ DENG2_PIMPL_NOREF(FontLineWrapping) return wrappedLines; } + Rangei findNextTabbedRange(int startLine) const + { + for(int i = startLine + 1; i < lines.size(); ++i) + { + if(lines[i]->tabsDisabled()) return Rangei(startLine, i); + } + return Rangei(startLine, lines.size()); + } + /** * Wraps a range of lines that contains tab stops. Wrapping takes into * account the space available for each tab stop. @@ -592,7 +600,18 @@ void FontLineWrapping::wrapTextToWidth(String const &text, Font::RichFormat cons pos = wholeLine.end + 1; } - d->wrapLinesWithTabs(Rangei(0, d->lines.size())); + // Process the content is distinct ranges divided by untabbed content. + Rangei tabRange = d->findNextTabbedRange(0); + forever + { + int end = d->wrapLinesWithTabs(tabRange); + if(end == d->lines.size()) + { + // All lines processed. + break; + } + tabRange = d->findNextTabbedRange(end); + } } else { @@ -741,7 +760,7 @@ FontLineWrapping::LineInfo const &FontLineWrapping::lineInfo(int index) const int FontLineWrapping::LineInfo::highestTabStop() const { - int stop = 0; + int stop = -1; foreach(Segment const &seg, segs) { stop = de::max(stop, seg.tabStop); diff --git a/doomsday/client/src/ui/widgets/gltextcomposer.cpp b/doomsday/client/src/ui/widgets/gltextcomposer.cpp index 683be77d10..c94975d366 100644 --- a/doomsday/client/src/ui/widgets/gltextcomposer.cpp +++ b/doomsday/client/src/ui/widgets/gltextcomposer.cpp @@ -47,7 +47,7 @@ DENG2_PIMPL(GLTextComposer) Segment() : id(Id::None), x(0), width(0), compressed(false) {} int right() const { return x + width; } }; - QList segs; + QList segs; }; typedef QList Lines; Lines lines; @@ -211,11 +211,43 @@ DENG2_PIMPL(GLTextComposer) void updateLineLayout(Rangei const &lineRange) { + Rangei current = lineRange; + forever + { + int end = updateLineLayoutUntilUntabbed(current); + if(end == lineRange.end) + { + break; // Whole range done. + } + current = Rangei(end, lineRange.end); + } + } + + /** + * Attempts to update lines in the specified range, but stops if an + * untabbed line is encountered. This ensures that each distinct tabbed + * content subrange uses its own alignment. + * + * @param lineRange Range of lines to update. + * + * @return The actual end of the updated range. + */ + inline int updateLineLayoutUntilUntabbed(Rangei const &lineRange) + { + int rangeEnd = lineRange.end; + // Find the highest tab in use and initialize seg widths. int highestTab = 0; for(int i = lineRange.start; i < lineRange.end; ++i) { - highestTab = de::max(highestTab, wraps->lineInfo(i).highestTabStop()); + int lineStop = wraps->lineInfo(i).highestTabStop(); + if(lineStop < 0) + { + // An untabbed line will halt the process for now. + rangeEnd = de::max(i, lineRange.start + 1); + break; + } + highestTab = de::max(highestTab, lineStop); // Initialize the segments with indentation. for(int k = 0; k < lines[i].segs.size(); ++k) @@ -224,8 +256,10 @@ DENG2_PIMPL(GLTextComposer) } } + DENG2_ASSERT(rangeEnd > lineRange.start); + // Set segment X coordinates by stacking them left-to-right on each line. - for(int i = lineRange.start; i < lineRange.end; ++i) + for(int i = lineRange.start; i < rangeEnd; ++i) { if(lines[i].segs.isEmpty()) continue; @@ -244,7 +278,7 @@ DENG2_PIMPL(GLTextComposer) int maxRight = 0; // Find the maximum right edge for this spot. - for(int i = lineRange.start; i < lineRange.end; ++i) + for(int i = lineRange.start; i < rangeEnd; ++i) { FontLineWrapping::LineInfo const &info = wraps->lineInfo(i); for(int k = 0; k < info.segs.size(); ++k) @@ -258,7 +292,7 @@ DENG2_PIMPL(GLTextComposer) } // Move the segments to this position. - for(int i = lineRange.start; i < lineRange.end; ++i) + for(int i = lineRange.start; i < rangeEnd; ++i) { int localRight = maxRight; @@ -273,6 +307,8 @@ DENG2_PIMPL(GLTextComposer) } } } + + return rangeEnd; } }; From d2881d26db8e33406c41def093809dab0127d069 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 12:39:53 +0300 Subject: [PATCH 05/14] libgui|Drawable: Querying if a buffer exists --- doomsday/libgui/include/de/gui/drawable.h | 2 ++ doomsday/libgui/src/drawable.cpp | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/doomsday/libgui/include/de/gui/drawable.h b/doomsday/libgui/include/de/gui/drawable.h index 725fbbd169..31c23d0a28 100644 --- a/doomsday/libgui/include/de/gui/drawable.h +++ b/doomsday/libgui/include/de/gui/drawable.h @@ -101,6 +101,8 @@ class LIBGUI_PUBLIC Drawable : public AssetGroup Ids allPrograms() const; Ids allStates() const; + bool hasBuffer(Id id) const; + /** * Finds an existing buffer. * @param id Identifier of the buffer. diff --git a/doomsday/libgui/src/drawable.cpp b/doomsday/libgui/src/drawable.cpp index 2592daf1d5..60df5d51d3 100644 --- a/doomsday/libgui/src/drawable.cpp +++ b/doomsday/libgui/src/drawable.cpp @@ -161,6 +161,11 @@ Drawable::Ids Drawable::allStates() const return d->states.keys(); } +bool Drawable::hasBuffer(Id id) const +{ + return d->buffers.contains(id); +} + GLBuffer &Drawable::buffer(Id id) const { DENG2_ASSERT(d->buffers.contains(id)); From d81079e2d42ceeb7d1153d2258fe55274db94249 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 12:40:17 +0300 Subject: [PATCH 06/14] Client|GL: Composing OpenGL information as a styled text string --- doomsday/client/include/gl/sys_opengl.h | 8 +++++++ doomsday/client/src/gl/sys_opengl.cpp | 30 ++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/doomsday/client/include/gl/sys_opengl.h b/doomsday/client/include/gl/sys_opengl.h index fa798d11f8..a212e3d9b8 100644 --- a/doomsday/client/include/gl/sys_opengl.h +++ b/doomsday/client/include/gl/sys_opengl.h @@ -213,6 +213,14 @@ boolean Sys_GLCheckError(void); #ifdef __cplusplus } // extern "C" + +/** + * Information about the OpenGL driver and its capabilities. + * + * @return Styled text. + */ +de::String Sys_GLDescription(); + #endif #endif /* LIBDENG_SYSTEM_OPENGL_H */ diff --git a/doomsday/client/src/gl/sys_opengl.cpp b/doomsday/client/src/gl/sys_opengl.cpp index 81481ff3a1..268c1ac353 100644 --- a/doomsday/client/src/gl/sys_opengl.cpp +++ b/doomsday/client/src/gl/sys_opengl.cpp @@ -176,31 +176,25 @@ static void initialize(void) #endif } -static void printGLUInfo(void) -{ - GLfloat fVals[2]; - GLint iVal; +#define TABBED(A, B) _E(Ta) " " A " " _E(Tb) << B << "\n" +de::String Sys_GLDescription() +{ DENG_ASSERT_IN_MAIN_THREAD(); DENG_ASSERT_GL_CONTEXT_ACTIVE(); - LOG_MSG(_E(b) "OpenGL information:"); - de::String str; QTextStream os(&str); -#define TABBED(A, B) _E(Ta) " " A " " _E(Tb) << B << "\n" + os << _E(b) "OpenGL information:\n" << _E(.); os << TABBED("Version:", (char const *) glGetString(GL_VERSION)); os << TABBED("Renderer:", (char const *) glGetString(GL_RENDERER)); os << TABBED("Vendor:", (char const *) glGetString(GL_VENDOR)); - LOG_MSG("%s") << str.rightStrip(); - - str.clear(); - os.setString(&str); + os << _E(T`) "Capabilities:\n"; - os << "Capabilities:\n"; + GLint iVal; #ifdef USE_TEXTURE_COMPRESSION_S3 if(GL_state.extensions.texCompressionS3) @@ -226,19 +220,25 @@ static void printGLUInfo(void) glGetIntegerv(GL_MAX_TEXTURE_SIZE, &iVal); os << TABBED("Maximum texture size:", iVal); + GLfloat fVals[2]; glGetFloatv(GL_LINE_WIDTH_GRANULARITY, fVals); os << TABBED("Line width granularity:", fVals[0]); glGetFloatv(GL_LINE_WIDTH_RANGE, fVals); os << TABBED("Line width range:", fVals[0] << "..." << fVals[1]); - LOG_MSG("%s") << str.rightStrip(); - - Sys_GLPrintExtensions(); + return str.rightStrip(); #undef TABBED } +static void printGLUInfo(void) +{ + LOG_MSG("%s") << Sys_GLDescription(); + + Sys_GLPrintExtensions(); +} + #if 0 #ifdef WIN32 static void testMultisampling(HDC hDC) From 52c309dcf719907de317bcb69a245181c8fa9722 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 12:41:08 +0300 Subject: [PATCH 07/14] UI|Client|PopupWidget: "Info" style for popups A popup widget may now configure itself for "informational" use. --- doomsday/client/data/defaultstyle.pack/colors.dei | 15 +++++++++++---- doomsday/client/include/ui/widgets/popupwidget.h | 6 ++++++ .../src/ui/widgets/consolecommandwidget.cpp | 3 +++ doomsday/client/src/ui/widgets/popupwidget.cpp | 8 ++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/doomsday/client/data/defaultstyle.pack/colors.dei b/doomsday/client/data/defaultstyle.pack/colors.dei index 623cdea519..1180f84b77 100644 --- a/doomsday/client/data/defaultstyle.pack/colors.dei +++ b/doomsday/client/data/defaultstyle.pack/colors.dei @@ -30,6 +30,13 @@ group label { color dimaccent { rgb <0.85, 0.68, 0.34> } } +group popup { + group info { + color background { rgb <1.0, 1.0, 1.0> } + color glow { rgb $= inverted.glow.rgb } + } +} + group choice { color popup { rgb $= gui.colorAlpha(background.rgb, 1.0) } } @@ -54,10 +61,10 @@ group editor { color cursor { rgb $= gui.colorAlpha(accent.rgb, 0.7) } color hint { rgb $= textaccent.rgb } - group completion { - color background { rgb <1.0, 1.0, 1.0> } - color glow { rgb $= inverted.glow.rgb } - } + #group completion { + # color background { rgb <1.0, 1.0, 1.0> } + # color glow { rgb $= inverted.glow.rgb } + #} } group log { diff --git a/doomsday/client/include/ui/widgets/popupwidget.h b/doomsday/client/include/ui/widgets/popupwidget.h index 803d462dd0..a1c8a06ed0 100644 --- a/doomsday/client/include/ui/widgets/popupwidget.h +++ b/doomsday/client/include/ui/widgets/popupwidget.h @@ -85,6 +85,12 @@ class PopupWidget : public GuiWidget */ void setClickToClose(bool clickCloses); + /** + * Sets the style of the popup to the one used for informational popups + * rather than interactive (the default) ones. + */ + void useInfoStyle(); + // Events. void viewResized(); void update(); diff --git a/doomsday/client/src/ui/widgets/consolecommandwidget.cpp b/doomsday/client/src/ui/widgets/consolecommandwidget.cpp index 19740b7975..2bff4b09f8 100644 --- a/doomsday/client/src/ui/widgets/consolecommandwidget.cpp +++ b/doomsday/client/src/ui/widgets/consolecommandwidget.cpp @@ -50,10 +50,13 @@ public IGameChangeObserver completions->setMaximumLineWidth(640); popup = new PopupWidget; + popup->useInfoStyle(); + /* popup->set(Background(st.colors().colorf("editor.completion.background"), Background::BorderGlow, st.colors().colorf("editor.completion.glow"), st.rules().rule("glow").valuei())); + */ popup->setContent(completions); // Height for the content: depends on the document height (plus margins), but at diff --git a/doomsday/client/src/ui/widgets/popupwidget.cpp b/doomsday/client/src/ui/widgets/popupwidget.cpp index 2e5fd8d1c2..88e48d246a 100644 --- a/doomsday/client/src/ui/widgets/popupwidget.cpp +++ b/doomsday/client/src/ui/widgets/popupwidget.cpp @@ -323,6 +323,14 @@ void PopupWidget::setClickToClose(bool clickCloses) d->clickToClose = clickCloses; } +void PopupWidget::useInfoStyle() +{ + set(Background(style().colors().colorf("popup.info.background"), + Background::BorderGlow, + style().colors().colorf("popup.info.glow"), + style().rules().rule("glow").valuei())); +} + void PopupWidget::viewResized() { GuiWidget::viewResized(); From 2f9e8fdccaa31d30a1c8f0152fa1e767a6bc7277 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 12:41:37 +0300 Subject: [PATCH 08/14] UI|Client|DocumentWidget: Widget's height defaults to full content height --- doomsday/client/include/ui/widgets/documentwidget.h | 2 ++ doomsday/client/src/ui/widgets/documentwidget.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doomsday/client/include/ui/widgets/documentwidget.h b/doomsday/client/include/ui/widgets/documentwidget.h index 91e75e793c..97095e4c1b 100644 --- a/doomsday/client/include/ui/widgets/documentwidget.h +++ b/doomsday/client/include/ui/widgets/documentwidget.h @@ -32,6 +32,8 @@ * DocumentWidget can be configured to expand horizontally, or it can use a * certain determined fixed width (see DocumentWidget::setWidthPolicy()). * + * By default, the height of the widget is determined by its content size. + * * The assumption is that the source document is largely static so that once * prepared, the GL resources can be reused as many times as possible. */ diff --git a/doomsday/client/src/ui/widgets/documentwidget.cpp b/doomsday/client/src/ui/widgets/documentwidget.cpp index c45b69e7c6..2dd288ae1c 100644 --- a/doomsday/client/src/ui/widgets/documentwidget.cpp +++ b/doomsday/client/src/ui/widgets/documentwidget.cpp @@ -306,6 +306,8 @@ public Font::RichFormat::IStyle DocumentWidget::DocumentWidget(String const &name) : d(new Instance(this)) { setWidthPolicy(ui::Expand); + + rule().setInput(Rule::Height, contentRule().height() + margin(ui::Up) + margin(ui::Down)); } void DocumentWidget::setText(String const &styledText) @@ -316,7 +318,10 @@ void DocumentWidget::setText(String const &styledText) d->tasks.waitForDone(); // Show the progress indicator until the text is ready for drawing. - d->drawable.buffer(ID_TEXT).clear(); + if(d->drawable.hasBuffer(ID_TEXT)) + { + d->drawable.buffer(ID_TEXT).clear(); + } d->progress->show(); int indSize = style().rules().rule("document.progress").valuei(); setContentSize(Vector2i(indSize, indSize)); From 585fac39c62a349b4bf973bc47bf888886719687 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 12:42:41 +0300 Subject: [PATCH 09/14] UI|Client: Show GL information in the About dialog ContextWidgetOrganizer can now find items' widgets based on the item label. --- .../client/include/ui/widgets/aboutdialog.h | 8 +++++ .../ui/widgets/contextwidgetorganizer.h | 2 +- .../client/src/ui/widgets/aboutdialog.cpp | 33 +++++++++++++++++-- .../src/ui/widgets/contextwidgetorganizer.cpp | 17 ++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/doomsday/client/include/ui/widgets/aboutdialog.h b/doomsday/client/include/ui/widgets/aboutdialog.h index bf9c4a8ea0..89231f9f24 100644 --- a/doomsday/client/include/ui/widgets/aboutdialog.h +++ b/doomsday/client/include/ui/widgets/aboutdialog.h @@ -26,8 +26,16 @@ */ class AboutDialog : public DialogWidget { + Q_OBJECT + public: AboutDialog(); + +protected slots: + void showGLInfo(); + +private: + DENG2_PRIVATE(d) }; #endif // DENG_CLIENT_ABOUTDIALOG_H diff --git a/doomsday/client/include/ui/widgets/contextwidgetorganizer.h b/doomsday/client/include/ui/widgets/contextwidgetorganizer.h index b21b1545ba..b4c761f898 100644 --- a/doomsday/client/include/ui/widgets/contextwidgetorganizer.h +++ b/doomsday/client/include/ui/widgets/contextwidgetorganizer.h @@ -115,8 +115,8 @@ class ContextWidgetOrganizer ui::Context const &context() const; GuiWidget *itemWidget(ui::Context::Pos pos) const; - GuiWidget *itemWidget(ui::Item const &item) const; + GuiWidget *itemWidget(de::String const &label) const; private: DENG2_PRIVATE(d) diff --git a/doomsday/client/src/ui/widgets/aboutdialog.cpp b/doomsday/client/src/ui/widgets/aboutdialog.cpp index 0479e5e4d8..4734b9394d 100644 --- a/doomsday/client/src/ui/widgets/aboutdialog.cpp +++ b/doomsday/client/src/ui/widgets/aboutdialog.cpp @@ -19,9 +19,12 @@ #include "ui/widgets/aboutdialog.h" #include "ui/widgets/labelwidget.h" #include "ui/widgets/sequentiallayout.h" +#include "ui/widgets/popupwidget.h" +#include "ui/widgets/documentwidget.h" #include "ui/signalaction.h" #include "ui/style.h" #include "ui/signalaction.h" +#include "gl/sys_opengl.h" #include "clientapp.h" #include "versioninfo.h" @@ -31,7 +34,23 @@ using namespace de; -AboutDialog::AboutDialog() : DialogWidget("about") +DENG2_PIMPL(AboutDialog) +{ + PopupWidget *glPopup; + + Instance(Public *i) : Base(i) + { + // Popup with GL info. + glPopup = new PopupWidget; + glPopup->useInfoStyle(); + DocumentWidget *doc = new DocumentWidget; + doc->setText(Sys_GLDescription()); + glPopup->setContent(doc); + self.add(glPopup); + } +}; + +AboutDialog::AboutDialog() : DialogWidget("about"), d(new Instance(this)) { /* * Construct the widgets. @@ -64,7 +83,6 @@ AboutDialog::AboutDialog() : DialogWidget("about") ButtonWidget *homepage = new ButtonWidget; homepage->setText(tr("Go to Homepage")); - //homepage->setSizePolicy(ui::Expand, ui::Expand); homepage->setAction(new SignalAction(&ClientApp::app(), SLOT(openHomepageInBrowser()))); area().add(logo); @@ -88,5 +106,14 @@ AboutDialog::AboutDialog() : DialogWidget("about") area().setContentSize(layout.width(), layout.height() + homepage->rule().height()); buttons().items() - << new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Close")); + << new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Close")) + << new DialogButtonItem(DialogWidget::Action, tr("GL"), new SignalAction(this, SLOT(showGLInfo()))); + + // The GL popup is anchored to the button. + d->glPopup->setAnchorAndOpeningDirection(buttons().organizer().itemWidget(tr("GL"))->rule(), ui::Up); +} + +void AboutDialog::showGLInfo() +{ + d->glPopup->open(); } diff --git a/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp b/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp index 4b19caef01..1648821ad6 100644 --- a/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp +++ b/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp @@ -209,6 +209,18 @@ DENG2_OBSERVES(ui::Item, Change ) if(found == mapping.constEnd()) return 0; return found.value(); } + + GuiWidget *findByLabel(String const &label) const + { + DENG2_FOR_EACH_CONST(Mapping, i, mapping) + { + if(i.key()->label() == label) + { + return i.value(); + } + } + return 0; + } }; ContextWidgetOrganizer::ContextWidgetOrganizer(GuiWidget &container) @@ -252,6 +264,11 @@ GuiWidget *ContextWidgetOrganizer::itemWidget(ui::Item const &item) const return d->find(item); } +GuiWidget *ContextWidgetOrganizer::itemWidget(String const &label) const +{ + return d->findByLabel(label); +} + GuiWidget *DefaultWidgetFactory::makeItemWidget(ui::Item const &, GuiWidget const *) { return new LabelWidget; From b47daf46fa87328ebfa68e7ab2c13a044ae8d6bb Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 12:53:55 +0300 Subject: [PATCH 10/14] Client|Audio|UI: Show audio information in the About dialog --- doomsday/client/include/audio/audiodriver.h | 5 +++++ .../client/include/ui/widgets/aboutdialog.h | 1 + doomsday/client/src/audio/audiodriver.cpp | 12 +++++++---- .../client/src/ui/widgets/aboutdialog.cpp | 21 +++++++++++++++++-- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/doomsday/client/include/audio/audiodriver.h b/doomsday/client/include/audio/audiodriver.h index e8e71a70f7..414cc65b6f 100644 --- a/doomsday/client/include/audio/audiodriver.h +++ b/doomsday/client/include/audio/audiodriver.h @@ -33,6 +33,11 @@ #include "api_audiod_mus.h" #ifdef __cplusplus + +#include + +de::String AudioDriver_InterfaceDescription(); + extern "C" { #endif diff --git a/doomsday/client/include/ui/widgets/aboutdialog.h b/doomsday/client/include/ui/widgets/aboutdialog.h index 89231f9f24..6cd946f210 100644 --- a/doomsday/client/include/ui/widgets/aboutdialog.h +++ b/doomsday/client/include/ui/widgets/aboutdialog.h @@ -33,6 +33,7 @@ class AboutDialog : public DialogWidget protected slots: void showGLInfo(); + void showAudioInfo(); private: DENG2_PRIVATE(d) diff --git a/doomsday/client/src/audio/audiodriver.cpp b/doomsday/client/src/audio/audiodriver.cpp index f7ee0276b7..12034cf4a4 100644 --- a/doomsday/client/src/audio/audiodriver.cpp +++ b/doomsday/client/src/audio/audiodriver.cpp @@ -413,13 +413,13 @@ static void selectInterfaces(audiodriverid_t defaultDriverId) AudioDriver_Music_Set(AUDIOP_SFX_INTERFACE, AudioDriver_SFX()); } -void AudioDriver_PrintInterfaces(void) +de::String AudioDriver_InterfaceDescription() { - LOG_INFO(_E(b) "Audio configuration" _E(2) " (by decreasing priority):"); - de::String str; QTextStream os(&str); + os << _E(b) "Audio configuration" _E(2) " (by decreasing priority):\n" _E(.)_E(.); + for(int i = MAX_AUDIO_INTERFACES - 1; i >= 0; --i) { audiointerface_t* a = &activeInterfaces[i]; @@ -434,8 +434,12 @@ void AudioDriver_PrintInterfaces(void) << Str_Text(AudioDriver_InterfaceName(a->i.sfx)) << "\n"; } } + return str.rightStrip(); +} - LOG_MSG("%s") << str.rightStrip(); +void AudioDriver_PrintInterfaces(void) +{ + LOG_MSG("%s") << AudioDriver_InterfaceDescription(); } /* diff --git a/doomsday/client/src/ui/widgets/aboutdialog.cpp b/doomsday/client/src/ui/widgets/aboutdialog.cpp index 4734b9394d..d97cbea8af 100644 --- a/doomsday/client/src/ui/widgets/aboutdialog.cpp +++ b/doomsday/client/src/ui/widgets/aboutdialog.cpp @@ -25,6 +25,7 @@ #include "ui/style.h" #include "ui/signalaction.h" #include "gl/sys_opengl.h" +#include "audio/audiodriver.h" #include "clientapp.h" #include "versioninfo.h" @@ -37,6 +38,7 @@ using namespace de; DENG2_PIMPL(AboutDialog) { PopupWidget *glPopup; + PopupWidget *audioPopup; Instance(Public *i) : Base(i) { @@ -47,6 +49,14 @@ DENG2_PIMPL(AboutDialog) doc->setText(Sys_GLDescription()); glPopup->setContent(doc); self.add(glPopup); + + // Popup with audio info. + audioPopup = new PopupWidget; + audioPopup->useInfoStyle(); + doc = new DocumentWidget; + doc->setText(AudioDriver_InterfaceDescription()); + audioPopup->setContent(doc); + self.add(audioPopup); } }; @@ -107,13 +117,20 @@ AboutDialog::AboutDialog() : DialogWidget("about"), d(new Instance(this)) buttons().items() << new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Close")) - << new DialogButtonItem(DialogWidget::Action, tr("GL"), new SignalAction(this, SLOT(showGLInfo()))); + << new DialogButtonItem(DialogWidget::Action, tr("GL"), new SignalAction(this, SLOT(showGLInfo()))) + << new DialogButtonItem(DialogWidget::Action, tr("Audio"), new SignalAction(this, SLOT(showAudioInfo()))); - // The GL popup is anchored to the button. + // The popups are anchored to their button. d->glPopup->setAnchorAndOpeningDirection(buttons().organizer().itemWidget(tr("GL"))->rule(), ui::Up); + d->audioPopup->setAnchorAndOpeningDirection(buttons().organizer().itemWidget(tr("Audio"))->rule(), ui::Up); } void AboutDialog::showGLInfo() { d->glPopup->open(); } + +void AboutDialog::showAudioInfo() +{ + d->audioPopup->open(); +} From 5b8bf0f9fb17607daa8d0fe910a7e0ba2a025457 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 16:09:44 +0300 Subject: [PATCH 11/14] UI|Client|Widgets: ChoiceWidget's width depends on widest child item Also corrected some workarounds to issues that are no longer relevant, related to hidden children in a MenuWidget. There is no reason to not update or otherwise ignore hidden widgets any more. --- doomsday/client/include/ui/widgets/choicewidget.h | 1 - doomsday/client/src/ui/widgets/choicewidget.cpp | 8 +++++++- .../client/src/ui/widgets/contextwidgetorganizer.cpp | 12 ++++++------ doomsday/client/src/ui/widgets/dialogwidget.cpp | 5 ++++- doomsday/client/src/ui/widgets/gltextcomposer.cpp | 2 ++ doomsday/client/src/ui/widgets/labelwidget.cpp | 2 +- doomsday/client/src/ui/widgets/menuwidget.cpp | 4 ++-- 7 files changed, 22 insertions(+), 12 deletions(-) diff --git a/doomsday/client/include/ui/widgets/choicewidget.h b/doomsday/client/include/ui/widgets/choicewidget.h index 5783b5f7ac..df5ebea644 100644 --- a/doomsday/client/include/ui/widgets/choicewidget.h +++ b/doomsday/client/include/ui/widgets/choicewidget.h @@ -63,7 +63,6 @@ class ChoiceWidget : public ButtonWidget void setSelected(ui::Context::Pos pos); ui::Context::Pos selected() const; - ui::Item const &selectedItem() const; public slots: diff --git a/doomsday/client/src/ui/widgets/choicewidget.cpp b/doomsday/client/src/ui/widgets/choicewidget.cpp index 5c3779c7c7..db63a8b98d 100644 --- a/doomsday/client/src/ui/widgets/choicewidget.cpp +++ b/doomsday/client/src/ui/widgets/choicewidget.cpp @@ -27,7 +27,7 @@ DENG_GUI_PIMPL(ChoiceWidget), DENG2_OBSERVES(Context, Addition), DENG2_OBSERVES(Context, Removal), DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation) -{ +{ /** * Items in the choice's popup uses this as action to change the selected * item. @@ -61,6 +61,8 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation) Instance(Public *i) : Base(i), selected(Context::InvalidPos) { self.setFont("choice.selected"); + self.setSizePolicy(ui::Fixed, ui::Expand); + //self.setAlignment(ui::AlignLeft); choices = new PopupMenuWidget; choices->setAnchorAndOpeningDirection(self.hitRule(), ui::Right); @@ -69,6 +71,10 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation) choices->menu().organizer().audienceForWidgetCreation += this; self.add(choices); + // The choice button itself has the same width as the choice menu + // (i.e., the longest item's width). + self.rule().setInput(Rule::Width, choices->menu().rule().width()); + self.setAction(new SignalAction(thisPublic, SLOT(openPopup()))); updateButtonWithSelection(); diff --git a/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp b/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp index 1648821ad6..87be7fad2c 100644 --- a/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp +++ b/doomsday/client/src/ui/widgets/contextwidgetorganizer.cpp @@ -88,12 +88,6 @@ DENG2_OBSERVES(ui::Item, Change ) GuiWidget *w = factory->makeItemWidget(item, container); if(!w) return; // Unpresentable. - // Others may alter the widget in some way. - DENG2_FOR_PUBLIC_AUDIENCE(WidgetCreation, i) - { - i->widgetCreatedForItem(*w, item); - } - // Update the widget immediately. mapping.insert(&item, w); itemChanged(item); @@ -108,6 +102,12 @@ DENG2_OBSERVES(ui::Item, Change ) container->insertBefore(w, *mapping[&context->at(pos + 1)]); } + // Others may alter the widget in some way. + DENG2_FOR_PUBLIC_AUDIENCE(WidgetCreation, i) + { + i->widgetCreatedForItem(*w, item); + } + // Observe. w->audienceForDeletion += this; // in case it's manually deleted item.audienceForChange += this; diff --git a/doomsday/client/src/ui/widgets/dialogwidget.cpp b/doomsday/client/src/ui/widgets/dialogwidget.cpp index d106480a1d..2669065d9c 100644 --- a/doomsday/client/src/ui/widgets/dialogwidget.cpp +++ b/doomsday/client/src/ui/widgets/dialogwidget.cpp @@ -241,7 +241,10 @@ DENG2_OBSERVES(ui::Context, Removal) // All label-based widgets should expand on their own. if(LabelWidget *lab = w.maybeAs()) { - lab->setSizePolicy(ui::Expand, ui::Expand); + if(!w.is()) + { + lab->setSizePolicy(ui::Expand, ui::Expand); + } } // Toggles should have no background. diff --git a/doomsday/client/src/ui/widgets/gltextcomposer.cpp b/doomsday/client/src/ui/widgets/gltextcomposer.cpp index c94975d366..603b55b908 100644 --- a/doomsday/client/src/ui/widgets/gltextcomposer.cpp +++ b/doomsday/client/src/ui/widgets/gltextcomposer.cpp @@ -211,6 +211,8 @@ DENG2_PIMPL(GLTextComposer) void updateLineLayout(Rangei const &lineRange) { + if(lineRange.isEmpty()) return; + Rangei current = lineRange; forever { diff --git a/doomsday/client/src/ui/widgets/labelwidget.cpp b/doomsday/client/src/ui/widgets/labelwidget.cpp index dfcda288d7..6247fd5028 100644 --- a/doomsday/client/src/ui/widgets/labelwidget.cpp +++ b/doomsday/client/src/ui/widgets/labelwidget.cpp @@ -532,7 +532,7 @@ void LabelWidget::update() { GuiWidget::update(); - if(!isHidden()) + //if(!isHidden()) { d->update(); } diff --git a/doomsday/client/src/ui/widgets/menuwidget.cpp b/doomsday/client/src/ui/widgets/menuwidget.cpp index a153665099..aef9ec5510 100644 --- a/doomsday/client/src/ui/widgets/menuwidget.cpp +++ b/doomsday/client/src/ui/widgets/menuwidget.cpp @@ -227,7 +227,7 @@ public ContextWidgetOrganizer::IWidgetFactory { if(GuiWidget const *widget = child->maybeAs()) { - return widget->isVisible(); + return !widget->behavior().testFlag(Widget::Hidden); } return false; } @@ -336,7 +336,7 @@ ContextWidgetOrganizer const &MenuWidget::organizer() const void MenuWidget::update() { - if(isHidden()) return; + //if(isHidden()) return; if(d->needLayout) { From e6c3ccf51de8af358f168733044700edf4267808 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 17:59:35 +0300 Subject: [PATCH 12/14] Refactor|UI|Client: Added rule-based ui::Margins to handle widget margins Margins need to be rules, too, so that they can be used freely in rule expressions. Fixed code where an assumption was made that the margin is the same on all sides of a widget. --- doomsday/client/client.pro | 6 +- .../client/include/ui/widgets/guiwidget.h | 12 +- doomsday/client/include/ui/widgets/margins.h | 77 ++++++ doomsday/client/src/ui/clientwindow.cpp | 2 +- .../client/src/ui/widgets/aboutdialog.cpp | 2 +- .../src/ui/widgets/consolecommandwidget.cpp | 2 +- .../client/src/ui/widgets/dialogwidget.cpp | 10 +- .../client/src/ui/widgets/documentwidget.cpp | 12 +- doomsday/client/src/ui/widgets/guiwidget.cpp | 67 ++--- .../client/src/ui/widgets/labelwidget.cpp | 8 +- doomsday/client/src/ui/widgets/margins.cpp | 234 ++++++++++++++++++ doomsday/client/src/ui/widgets/menuwidget.cpp | 8 +- .../client/src/ui/widgets/popupmenuwidget.cpp | 6 +- .../client/src/ui/widgets/popupwidget.cpp | 8 +- .../src/updater/updatersettingsdialog.cpp | 2 +- 15 files changed, 371 insertions(+), 85 deletions(-) create mode 100644 doomsday/client/include/ui/widgets/margins.h create mode 100644 doomsday/client/src/ui/widgets/margins.cpp diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index 84027ac1ef..2e9f080636 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -362,6 +362,7 @@ DENG_HEADERS += \ include/ui/widgets/gridlayout.h \ include/ui/widgets/guirootwidget.h \ include/ui/widgets/guiwidget.h \ + include/ui/widgets/guiwidgetprivate.h \ include/ui/widgets/fontlinewrapping.h \ include/ui/widgets/item.h \ include/ui/widgets/labelwidget.h \ @@ -369,6 +370,7 @@ DENG_HEADERS += \ include/ui/widgets/lineeditwidget.h \ include/ui/widgets/listcontext.h \ include/ui/widgets/logwidget.h \ + include/ui/widgets/margins.h \ include/ui/widgets/menuwidget.h \ include/ui/widgets/messagedialog.h \ include/ui/widgets/notificationwidget.h \ @@ -441,8 +443,7 @@ INCLUDEPATH += \ HEADERS += \ $$DENG_API_HEADERS \ - $$DENG_HEADERS \ - include/ui/widgets/guiwidgetprivate.h + $$DENG_HEADERS # Platform-specific sources. win32 { @@ -685,6 +686,7 @@ SOURCES += \ src/ui/widgets/lineeditwidget.cpp \ src/ui/widgets/listcontext.cpp \ src/ui/widgets/logwidget.cpp \ + src/ui/widgets/margins.cpp \ src/ui/widgets/menuwidget.cpp \ src/ui/widgets/messagedialog.cpp \ src/ui/widgets/notificationwidget.cpp \ diff --git a/doomsday/client/include/ui/widgets/guiwidget.h b/doomsday/client/include/ui/widgets/guiwidget.h index 3d74e3d0d5..929652d516 100644 --- a/doomsday/client/include/ui/widgets/guiwidget.h +++ b/doomsday/client/include/ui/widgets/guiwidget.h @@ -26,6 +26,7 @@ #include "../uidefs.h" #include "ui/style.h" +#include "margins.h" #include "guiwidgetprivate.h" class GuiRootWidget; @@ -150,6 +151,9 @@ class GuiWidget : public QObject, public de::Widget */ de::RuleRectangle const &rule() const; + ui::Margins &margins(); + ui::Margins const &margins() const; + de::Rectanglef normalizedRect() const; /** @@ -162,20 +166,12 @@ class GuiWidget : public QObject, public de::Widget void setFont(de::DotPath const &id); void setTextColor(de::DotPath const &id); - void setMargin(de::DotPath const &id); - void setMargin(ui::Direction dir, de::DotPath const &id); - void setMargins(de::DotPath const &leftId, - de::DotPath const &topId, - de::DotPath const &rightId, - de::DotPath const &bottomId); void set(Background const &bg); de::Font const &font() const; de::ColorBank::Color textColor() const; de::ColorBank::Colorf textColorf() const; - de::Rule const &margin(ui::Direction dir = ui::Left) const; - /** * Determines whether the contents of the widget are supposed to be clipped * to its boundaries. The Widget::ContentClipping behavior flag is used for diff --git a/doomsday/client/include/ui/widgets/margins.h b/doomsday/client/include/ui/widgets/margins.h new file mode 100644 index 0000000000..de4a2412b4 --- /dev/null +++ b/doomsday/client/include/ui/widgets/margins.h @@ -0,0 +1,77 @@ +/** @file margins.h Margin rules for widgets. + * + * @authors Copyright (c) 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef DENG_CLIENT_UI_MARGINS_H +#define DENG_CLIENT_UI_MARGINS_H + +#include +#include + +#include "../uidefs.h" + +namespace ui { + +/** + * Margin rules for a widget. + */ +class Margins +{ +public: + DENG2_DEFINE_AUDIENCE(Change, void marginsChanged()) + +public: + Margins(de::String const &defaultMargin = "gap"); + + void setLeft (de::DotPath const &leftMarginId); + void setRight (de::DotPath const &rightMarginId); + void setTop (de::DotPath const &topMarginId); + void setBottom(de::DotPath const &bottomMarginId); + void set (ui::Direction dir, de::DotPath const &marginId); + void set (de::DotPath const &marginId); + + void setLeft (de::Rule const &rule); + void setRight (de::Rule const &rule); + void setTop (de::Rule const &rule); + void setBottom(de::Rule const &rule); + void set (ui::Direction dir, de::Rule const &rule); + void set (de::Rule const &rule); + + de::Rule const &left() const; + de::Rule const &right() const; + de::Rule const &top() const; + de::Rule const &bottom() const; + + /** + * The "width" of the margins is the sum of the left and right margins. + */ + de::Rule const &width() const; + + /** + * The "height" of the margins is the sim of the top and bottom margins. + */ + de::Rule const &height() const; + + de::Rule const &margin(ui::Direction dir) const; + +private: + DENG2_PRIVATE(d) +}; + +} // namespace ui + +#endif // DENG_CLIENT_UI_MARGINS_H diff --git a/doomsday/client/src/ui/clientwindow.cpp b/doomsday/client/src/ui/clientwindow.cpp index 664a87e12a..d4a8571a45 100644 --- a/doomsday/client/src/ui/clientwindow.cpp +++ b/doomsday/client/src/ui/clientwindow.cpp @@ -123,7 +123,7 @@ public IGameChangeObserver background->setImage(style.images().image("window.background")); background->setImageFit(ui::FitToSize); background->setSizePolicy(ui::Filled, ui::Filled); - background->setMargin(""); + background->margins().set(""); background->rule() .setInput(Rule::Left, root.viewLeft()) .setInput(Rule::Top, root.viewTop()) diff --git a/doomsday/client/src/ui/widgets/aboutdialog.cpp b/doomsday/client/src/ui/widgets/aboutdialog.cpp index d97cbea8af..ed4a58bf70 100644 --- a/doomsday/client/src/ui/widgets/aboutdialog.cpp +++ b/doomsday/client/src/ui/widgets/aboutdialog.cpp @@ -71,7 +71,7 @@ AboutDialog::AboutDialog() : DialogWidget("about"), d(new Instance(this)) // Set up the contents of the widget. LabelWidget *title = new LabelWidget; - title->setMargin(""); + title->margins().set(""); title->setFont("title"); title->setText(DOOMSDAY_NICENAME); title->setSizePolicy(ui::Fixed, ui::Expand); diff --git a/doomsday/client/src/ui/widgets/consolecommandwidget.cpp b/doomsday/client/src/ui/widgets/consolecommandwidget.cpp index 2bff4b09f8..36571c798c 100644 --- a/doomsday/client/src/ui/widgets/consolecommandwidget.cpp +++ b/doomsday/client/src/ui/widgets/consolecommandwidget.cpp @@ -65,7 +65,7 @@ public IGameChangeObserver OperatorRule::minimum( OperatorRule::minimum(st.rules().rule("editor.completion.height"), completions->contentRule().height() + - 2 * completions->margin()), + completions->margins().height()), self.rule().top() - st.rules().rule("gap"))); self.add(popup); diff --git a/doomsday/client/src/ui/widgets/dialogwidget.cpp b/doomsday/client/src/ui/widgets/dialogwidget.cpp index 2669065d9c..ae4e506149 100644 --- a/doomsday/client/src/ui/widgets/dialogwidget.cpp +++ b/doomsday/client/src/ui/widgets/dialogwidget.cpp @@ -117,13 +117,13 @@ DENG2_OBSERVES(ui::Context, Removal) area->rule() .setInput(Rule::Left, self.rule().left()) .setInput(Rule::Top, self.rule().top()) - .setInput(Rule::Width, area->contentRule().width() + area->margin() * 2) + .setInput(Rule::Width, area->contentRule().width() + area->margins().width()) .setInput(Rule::Height, container->rule().height() - buttons->rule().height() + - area->margin()); + area->margins().bottom()); // Buttons below the area. buttons->rule() - .setInput(Rule::Top, area->rule().bottom() - area->margin()) // overlap margins + .setInput(Rule::Top, area->rule().bottom() - area->margins().bottom()) // overlap margins .setInput(Rule::Right, self.rule().right()); // A blank container widget acts as the popup content parent. @@ -142,7 +142,7 @@ DENG2_OBSERVES(ui::Context, Removal) self.content().rule().setInput(Rule::Height, OperatorRule::minimum(root().viewHeight(), area->contentRule().height() + - area->margin() + + area->margins().bottom() + buttons->rule().height())); } @@ -236,7 +236,7 @@ DENG2_OBSERVES(ui::Context, Removal) { GuiWidget &w = areaChild.as(); - w.setMargin("dialog.gap"); + w.margins().set("dialog.gap"); // All label-based widgets should expand on their own. if(LabelWidget *lab = w.maybeAs()) diff --git a/doomsday/client/src/ui/widgets/documentwidget.cpp b/doomsday/client/src/ui/widgets/documentwidget.cpp index 2dd288ae1c..69eb964e38 100644 --- a/doomsday/client/src/ui/widgets/documentwidget.cpp +++ b/doomsday/client/src/ui/widgets/documentwidget.cpp @@ -226,8 +226,6 @@ public Font::RichFormat::IStyle if(!self.geometryRequested()) return; - int const margin = self.margin().valuei(); - // Background and scroll indicator. VertexBuf::Builder verts; self.glMakeGeometry(verts); @@ -247,7 +245,7 @@ public Font::RichFormat::IStyle } else { - wrapWidth = self.rule().width().valuei() - 2 * margin; + wrapWidth = self.rule().width().valuei() - self.margins().width().valuei(); } if(wraps.isEmpty() || wraps.maximumWidth() != wrapWidth) @@ -307,7 +305,7 @@ DocumentWidget::DocumentWidget(String const &name) : d(new Instance(this)) { setWidthPolicy(ui::Expand); - rule().setInput(Rule::Height, contentRule().height() + margin(ui::Up) + margin(ui::Down)); + rule().setInput(Rule::Height, contentRule().height() + margins().height()); } void DocumentWidget::setText(String const &styledText) @@ -343,7 +341,7 @@ void DocumentWidget::setWidthPolicy(ui::SizePolicy policy) if(policy == ui::Expand) { - rule().setInput(Rule::Width, contentRule().width() + 2 * margin()); + rule().setInput(Rule::Width, contentRule().width() + margins().width()); } else { @@ -394,8 +392,8 @@ void DocumentWidget::glMakeGeometry(DefaultVertexBuf::Builder &verts) { ScrollAreaWidget::glMakeGeometry(verts); - glMakeScrollIndicatorGeometry(verts, Vector2f(rule().left().value() + margin().value(), - rule().top().value() + margin().value())); + glMakeScrollIndicatorGeometry(verts, Vector2f(rule().left().value() + margins().left().value(), + rule().top().value() + margins().top().value())); } void DocumentWidget::updateStyle() diff --git a/doomsday/client/src/ui/widgets/guiwidget.cpp b/doomsday/client/src/ui/widgets/guiwidget.cpp index e140b62eeb..19dc2e08d8 100644 --- a/doomsday/client/src/ui/widgets/guiwidget.cpp +++ b/doomsday/client/src/ui/widgets/guiwidget.cpp @@ -28,13 +28,15 @@ using namespace de; -DENG2_PIMPL(GuiWidget) +DENG2_PIMPL(GuiWidget), +DENG2_OBSERVES(ui::Margins, Change) #ifdef DENG2_DEBUG , DENG2_OBSERVES(Widget, ParentChange) #endif { RuleRectangle rule; ///< Visual rule, used when drawing. RuleRectangle hitRule; ///< Used only for hit testing. By default matches the visual rule. + ui::Margins margins; Rectanglei savedPos; bool inited; bool needGeometry; @@ -45,10 +47,6 @@ DENG2_PIMPL(GuiWidget) // Style. DotPath fontId; DotPath textColorId; - DotPath marginLeftId; - DotPath marginTopId; - DotPath marginRightId; - DotPath marginBottomId; // Background blurring. bool blurInited; @@ -64,16 +62,13 @@ DENG2_PIMPL(GuiWidget) Instance(Public *i) : Base(i), + margins("gap"), inited(false), needGeometry(true), styleChanged(false), opacity(1.f, Animation::Linear), fontId("default"), textColorId("text"), - marginLeftId("gap"), - marginTopId("gap"), - marginRightId("gap"), - marginBottomId("gap"), blurInited(false), uBlurMvpMatrix("uMvpMatrix", GLUniform::Mat4), uBlurColor ("uColor", GLUniform::Vec4), @@ -81,6 +76,8 @@ DENG2_PIMPL(GuiWidget) uBlurStep ("uBlurStep", GLUniform::Vec2), uBlurWindow ("uWindow", GLUniform::Vec4) { + margins.audienceForChange += this; + #ifdef DENG2_DEBUG self.audienceForParentChange += this; rule.setDebugName(self.path()); @@ -109,6 +106,11 @@ DENG2_PIMPL(GuiWidget) #endif } + void marginsChanged() + { + styleChanged = true; + } + #ifdef DENG2_DEBUG void widgetParentChanged(Widget &, Widget *, Widget *) { @@ -297,56 +299,30 @@ ColorBank::Colorf GuiWidget::textColorf() const return style().colors().colorf(d->textColorId); } -Rule const &GuiWidget::margin(ui::Direction dir) const -{ - return style().rules().rule( - dir == ui::Left? d->marginLeftId : - dir == ui::Up? d->marginTopId : - dir == ui::Right? d->marginRightId : - d->marginBottomId); -} - void GuiWidget::setTextColor(DotPath const &id) { d->textColorId = id; d->styleChanged = true; } -void GuiWidget::setMargin(DotPath const &id) -{ - setMargins(id, id, id, id); -} - -void GuiWidget::setMargin(ui::Direction dir, DotPath const &id) +RuleRectangle &GuiWidget::rule() { - switch(dir) - { - case ui::Left: d->marginLeftId = id; break; - case ui::Up: d->marginTopId = id; break; - case ui::Right: d->marginRightId = id; break; - case ui::Down: d->marginBottomId = id; break; - default: return; - } - d->styleChanged = true; + return d->rule; } -void GuiWidget::setMargins(DotPath const &leftId, DotPath const &topId, DotPath const &rightId, DotPath const &bottomId) +RuleRectangle const &GuiWidget::rule() const { - d->marginLeftId = leftId; - d->marginTopId = topId; - d->marginRightId = rightId; - d->marginBottomId = bottomId; - d->styleChanged = true; + return d->rule; } -RuleRectangle &GuiWidget::rule() +ui::Margins &GuiWidget::margins() { - return d->rule; + return d->margins; } -RuleRectangle const &GuiWidget::rule() const +ui::Margins const &GuiWidget::margins() const { - return d->rule; + return d->margins; } Rectanglef GuiWidget::normalizedRect() const @@ -361,7 +337,10 @@ Rectanglef GuiWidget::normalizedRect() const Rectanglef GuiWidget::normalizedContentRect() const { - Rectanglef const rect = rule().rect().shrunk(margin().valuei()); + Rectanglef const rect = rule().rect().adjusted( Vector2f(margins().left().value(), + margins().top().value()), + -Vector2f(margins().right().value(), + margins().bottom().value())); GuiRootWidget::Size const &viewSize = root().viewSize(); return Rectanglef(Vector2f(float(rect.left()) / float(viewSize.x), float(rect.top()) / float(viewSize.y)), diff --git a/doomsday/client/src/ui/widgets/labelwidget.cpp b/doomsday/client/src/ui/widgets/labelwidget.cpp index 6247fd5028..720089c208 100644 --- a/doomsday/client/src/ui/widgets/labelwidget.cpp +++ b/doomsday/client/src/ui/widgets/labelwidget.cpp @@ -102,10 +102,10 @@ public Font::RichFormat::IStyle { Style const &st = self.style(); - tlMargin = Vector2i(self.margin(ui::Left).valuei(), - self.margin(ui::Up).valuei()); - brMargin = Vector2i(self.margin(ui::Right).valuei(), - self.margin(ui::Down).valuei()); + tlMargin = Vector2i(self.margins().left().valuei(), + self.margins().top().valuei()); + brMargin = Vector2i(self.margins().right().valuei(), + self.margins().bottom().valuei()); gap = st.rules().rule(gapId).valuei(); diff --git a/doomsday/client/src/ui/widgets/margins.cpp b/doomsday/client/src/ui/widgets/margins.cpp new file mode 100644 index 0000000000..2628c2a28b --- /dev/null +++ b/doomsday/client/src/ui/widgets/margins.cpp @@ -0,0 +1,234 @@ +/** @file margins.cpp + * + * @authors Copyright (c) 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "ui/widgets/margins.h" +#include "clientapp.h" + +using namespace de; + +namespace ui { + +enum Side +{ + SideLeft, + SideRight, + SideTop, + SideBottom, + + LeftRight, + TopBottom, + + MAX_SIDES +}; + +DENG2_PIMPL(Margins) +{ + Rule const *inputs[4]; + IndirectRule *outputs[MAX_SIDES]; + + Instance(Public *i, DotPath const &defaultId) : Base(i) + { + zap(inputs); + zap(outputs); + + for(int i = 0; i < 4; ++i) + { + setInput(i, defaultId); + } + } + + ~Instance() + { + for(int i = 0; i < 4; ++i) + { + releaseRef(inputs[i]); + } + for(int i = 0; i < int(MAX_SIDES); ++i) + { + if(outputs[i]) + { + outputs[i]->unsetSource(); + releaseRef(outputs[i]); + } + } + } + + void setInput(int side, DotPath const &styleId) + { + setInput(side, ClientApp::windowSystem().style().rules().rule(styleId)); + } + + void setInput(int side, Rule const &rule) + { + DENG2_ASSERT(side >= 0 && side < 4); + changeRef(inputs[side], rule); + updateOutput(side); + + DENG2_FOR_PUBLIC_AUDIENCE(Change, i) + { + i->marginsChanged(); + } + } + + void updateOutput(int side) + { + if(!outputs[side]) return; + + if(side == LeftRight || side == SideLeft || side == SideRight) + { + if(inputs[SideLeft] && inputs[SideRight]) + { + outputs[side]->setSource(*inputs[SideLeft] + *inputs[SideRight]); + } + } + else if(side == TopBottom || side == SideTop || side == SideBottom) + { + if(inputs[SideTop] && inputs[SideBottom]) + { + outputs[side]->setSource(*inputs[SideTop] + *inputs[SideBottom]); + } + } + + if(side < 4 && inputs[side]) + { + outputs[side]->setSource(*inputs[side]); + } + } + + Rule const &getOutput(int side) + { + if(!outputs[side]) + { + outputs[side] = new IndirectRule; + updateOutput(side); + } + return *outputs[side]; + } +}; + +Margins::Margins(String const &defaultMargin) : d(new Instance(this, defaultMargin)) +{} + +void Margins::set(Direction dir, DotPath const &marginId) +{ + d->setInput(dir == Left? SideLeft : + dir == Right? SideRight : + dir == Up? SideTop : SideBottom, marginId); +} + +void Margins::set(DotPath const &marginId) +{ + set(Left, marginId); + set(Right, marginId); + set(Up, marginId); + set(Down, marginId); +} + +void Margins::setLeft(DotPath const &leftMarginId) +{ + set(ui::Left, leftMarginId); +} + +void Margins::setRight(DotPath const &rightMarginId) +{ + set(ui::Right, rightMarginId); +} + +void Margins::setTop(DotPath const &topMarginId) +{ + set(ui::Up, topMarginId); +} + +void Margins::setBottom(DotPath const &bottomMarginId) +{ + set(ui::Down, bottomMarginId); +} + +void Margins::set(Direction dir, Rule const &rule) +{ + d->setInput(dir == Left? SideLeft : + dir == Right? SideRight : + dir == Up? SideTop : SideBottom, rule); +} + +void Margins::set(Rule const &rule) +{ + set(Left, rule); + set(Right, rule); + set(Up, rule); + set(Down, rule); +} + +void Margins::setLeft(Rule const &rule) +{ + set(ui::Left, rule); +} + +void Margins::setRight(Rule const &rule) +{ + set(ui::Right, rule); +} + +void Margins::setTop(Rule const &rule) +{ + set(ui::Up, rule); +} + +void Margins::setBottom(Rule const &rule) +{ + set(ui::Down, rule); +} + +Rule const &Margins::left() const +{ + return d->getOutput(SideLeft); +} + +Rule const &Margins::right() const +{ + return d->getOutput(SideRight); +} + +Rule const &Margins::top() const +{ + return d->getOutput(SideTop); +} + +Rule const &Margins::bottom() const +{ + return d->getOutput(SideBottom); +} + +Rule const &Margins::width() const +{ + return d->getOutput(LeftRight); +} + +Rule const &Margins::height() const +{ + return d->getOutput(TopBottom); +} + +Rule const &Margins::margin(Direction dir) const +{ + return d->getOutput(dir == Left? SideLeft : + dir == Right? SideRight : + dir == Up? SideTop : SideBottom); +} + +} // namespace ui diff --git a/doomsday/client/src/ui/widgets/menuwidget.cpp b/doomsday/client/src/ui/widgets/menuwidget.cpp index aef9ec5510..63765b088b 100644 --- a/doomsday/client/src/ui/widgets/menuwidget.cpp +++ b/doomsday/client/src/ui/widgets/menuwidget.cpp @@ -273,13 +273,13 @@ void MenuWidget::setGridSize(int columns, ui::SizePolicy columnPolicy, if(d->colPolicy == ui::Filled) { DENG2_ASSERT(columns > 0); - d->layout.setOverrideWidth((rule().width() - margin() * 2) / float(columns)); + d->layout.setOverrideWidth((rule().width() - margins().width()) / float(columns)); } if(d->rowPolicy == ui::Filled) { DENG2_ASSERT(rows > 0); - d->layout.setOverrideHeight((rule().height() - margin() * 2) / float(rows)); + d->layout.setOverrideHeight((rule().height() - margins().height()) / float(rows)); } d->needLayout = true; @@ -314,11 +314,11 @@ void MenuWidget::updateLayout() // Expanding policy causes the size of the menu widget to change. if(d->colPolicy == Expand) { - rule().setInput(Rule::Width, d->layout.width() + margin() * 2); + rule().setInput(Rule::Width, d->layout.width() + margins().width()); } if(d->rowPolicy == Expand) { - rule().setInput(Rule::Height, d->layout.height() + margin() * 2); + rule().setInput(Rule::Height, d->layout.height() + margins().height()); } d->needLayout = false; diff --git a/doomsday/client/src/ui/widgets/popupmenuwidget.cpp b/doomsday/client/src/ui/widgets/popupmenuwidget.cpp index 6972658245..e67c1ea1c6 100644 --- a/doomsday/client/src/ui/widgets/popupmenuwidget.cpp +++ b/doomsday/client/src/ui/widgets/popupmenuwidget.cpp @@ -58,7 +58,7 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetUpdate) if(ButtonWidget *b = widget.maybeAs()) { b->setSizePolicy(ui::Expand, ui::Expand); - b->setMargin("unit"); + b->margins().set("unit"); b->audienceForStateChange += this; @@ -77,12 +77,12 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetUpdate) // The label of a separator may change. if(item.label().isEmpty()) { - widget.setMargin(""); + widget.margins().set(""); widget.setFont("separator.empty"); } else { - widget.setMargin("halfunit"); + widget.margins().set("halfunit"); widget.setFont("separator.label"); } } diff --git a/doomsday/client/src/ui/widgets/popupwidget.cpp b/doomsday/client/src/ui/widgets/popupwidget.cpp index 88e48d246a..00166c09b7 100644 --- a/doomsday/client/src/ui/widgets/popupwidget.cpp +++ b/doomsday/client/src/ui/widgets/popupwidget.cpp @@ -128,8 +128,8 @@ DENG_GUI_PIMPL(PopupWidget) .setInput(Rule::Bottom, *anchorY - *marker) .setInput(Rule::Left, OperatorRule::clamped( *anchorX - self.rule().width() / 2, - self.margin(), - self.root().viewWidth() - self.rule().width() - self.margin())); + self.margins().left(), + self.root().viewWidth() - self.rule().width() - self.margins().right())); break; case ui::Down: @@ -137,8 +137,8 @@ DENG_GUI_PIMPL(PopupWidget) .setInput(Rule::Top, *anchorY + *marker) .setInput(Rule::Left, OperatorRule::clamped( *anchorX - self.rule().width() / 2, - self.margin(), - self.root().viewWidth() - self.rule().width() - self.margin())); + self.margins().left(), + self.root().viewWidth() - self.rule().width() - self.margins().right())); break; case ui::Left: diff --git a/doomsday/client/src/updater/updatersettingsdialog.cpp b/doomsday/client/src/updater/updatersettingsdialog.cpp index 3e3a8af0fa..2120889a5c 100644 --- a/doomsday/client/src/updater/updatersettingsdialog.cpp +++ b/doomsday/client/src/updater/updatersettingsdialog.cpp @@ -81,7 +81,7 @@ DENG2_OBSERVES(ToggleWidget, Toggle) << new ChoiceItem(tr("Weekly"), UpdaterSettings::Weekly) << new ChoiceItem(tr("Monthly"), UpdaterSettings::Monthly); - lastChecked->setMargin(ui::Up, ""); + lastChecked->margins().setTop(""); releaseLabel->setText("Release type:"); From 8e9e1847be479740a2d9dc637498971699e850de Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 20:45:20 +0300 Subject: [PATCH 13/14] UI|Client: Fixed DialogWidget custom actions, ChoiceWidget sizing --- .../client/src/ui/widgets/choicewidget.cpp | 6 ------ .../client/src/ui/widgets/dialogwidget.cpp | 21 +++++++++---------- .../client/src/updater/downloaddialog.cpp | 2 +- doomsday/client/src/updater/updater.cpp | 2 +- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/doomsday/client/src/ui/widgets/choicewidget.cpp b/doomsday/client/src/ui/widgets/choicewidget.cpp index db63a8b98d..3ee705465f 100644 --- a/doomsday/client/src/ui/widgets/choicewidget.cpp +++ b/doomsday/client/src/ui/widgets/choicewidget.cpp @@ -61,8 +61,6 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation) Instance(Public *i) : Base(i), selected(Context::InvalidPos) { self.setFont("choice.selected"); - self.setSizePolicy(ui::Fixed, ui::Expand); - //self.setAlignment(ui::AlignLeft); choices = new PopupMenuWidget; choices->setAnchorAndOpeningDirection(self.hitRule(), ui::Right); @@ -71,10 +69,6 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation) choices->menu().organizer().audienceForWidgetCreation += this; self.add(choices); - // The choice button itself has the same width as the choice menu - // (i.e., the longest item's width). - self.rule().setInput(Rule::Width, choices->menu().rule().width()); - self.setAction(new SignalAction(thisPublic, SLOT(openPopup()))); updateButtonWithSelection(); diff --git a/doomsday/client/src/ui/widgets/dialogwidget.cpp b/doomsday/client/src/ui/widgets/dialogwidget.cpp index ae4e506149..141a8c5a40 100644 --- a/doomsday/client/src/ui/widgets/dialogwidget.cpp +++ b/doomsday/client/src/ui/widgets/dialogwidget.cpp @@ -176,14 +176,16 @@ DENG2_OBSERVES(ui::Context, Removal) if(ButtonItem const *i = item.maybeAs()) { ButtonWidget &but = widget.as(); - - if(i->role().testFlag(Accept)) - { - but.setAction(new SignalAction(thisPublic, SLOT(accept()))); - } - else if(i->role().testFlag(Reject)) + if(!i->action()) { - but.setAction(new SignalAction(thisPublic, SLOT(reject()))); + if(i->role().testFlag(Accept)) + { + but.setAction(new SignalAction(thisPublic, SLOT(accept()))); + } + else if(i->role().testFlag(Reject)) + { + but.setAction(new SignalAction(thisPublic, SLOT(reject()))); + } } } } @@ -241,10 +243,7 @@ DENG2_OBSERVES(ui::Context, Removal) // All label-based widgets should expand on their own. if(LabelWidget *lab = w.maybeAs()) { - if(!w.is()) - { - lab->setSizePolicy(ui::Expand, ui::Expand); - } + lab->setSizePolicy(ui::Expand, ui::Expand); } // Toggles should have no background. diff --git a/doomsday/client/src/updater/downloaddialog.cpp b/doomsday/client/src/updater/downloaddialog.cpp index 706e528482..d179a87431 100644 --- a/doomsday/client/src/updater/downloaddialog.cpp +++ b/doomsday/client/src/updater/downloaddialog.cpp @@ -1,4 +1,4 @@ -/** @file downloaddialog.cpp Dialog that downloads a distribution package. +/** @file downloaddialog.cpp Dialog that downloads a distribution package. * @ingroup updater * * @authors Copyright © 2012-2013 Jaakko Keränen diff --git a/doomsday/client/src/updater/updater.cpp b/doomsday/client/src/updater/updater.cpp index 4d23105382..70d6903500 100644 --- a/doomsday/client/src/updater/updater.cpp +++ b/doomsday/client/src/updater/updater.cpp @@ -1,4 +1,4 @@ -/** @file updater.cpp Automatic updater that works with dengine.net. +/** @file updater.cpp Automatic updater that works with dengine.net. * @ingroup updater * * When one of the updater dialogs is shown, the main window is automatically From 33d0145d87cc679cc688fbac7e6ff6eedbc69137 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sat, 24 Aug 2013 21:19:15 +0300 Subject: [PATCH 14/14] Fixed|GLTextComposer: Segment alignment for untabbed lines All segments now default to tab stop -1 if they don't use tabs. --- .../client/src/ui/widgets/gltextcomposer.cpp | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/doomsday/client/src/ui/widgets/gltextcomposer.cpp b/doomsday/client/src/ui/widgets/gltextcomposer.cpp index 603b55b908..5748adcbac 100644 --- a/doomsday/client/src/ui/widgets/gltextcomposer.cpp +++ b/doomsday/client/src/ui/widgets/gltextcomposer.cpp @@ -236,6 +236,7 @@ DENG2_PIMPL(GLTextComposer) */ inline int updateLineLayoutUntilUntabbed(Rangei const &lineRange) { + bool includesTabbedLines = false; int rangeEnd = lineRange.end; // Find the highest tab in use and initialize seg widths. @@ -243,11 +244,26 @@ DENG2_PIMPL(GLTextComposer) for(int i = lineRange.start; i < lineRange.end; ++i) { int lineStop = wraps->lineInfo(i).highestTabStop(); + if(lineStop >= 0) + { + // The range now includes at least one tabbed line. + includesTabbedLines = true; + } if(lineStop < 0) { - // An untabbed line will halt the process for now. - rangeEnd = de::max(i, lineRange.start + 1); - break; + // This is an untabbed line. + if(!includesTabbedLines) + { + // We can do many untabbed lines in the range as long as + // there are no tabbed ones. + rangeEnd = i + 1; + } + else + { + // An untabbed line will halt the process for now. + rangeEnd = de::max(i, lineRange.start + 1); + break; + } } highestTab = de::max(highestTab, lineStop); @@ -473,7 +489,7 @@ void GLTextComposer::makeVertices(Vertices &triStrip, // Line alignment. /// @todo How to center/right-align text that uses tab stops? - if(line.segs.size() == 1 && !d->wraps->lineInfo(0).segs[0].tabStop) + if(line.segs.size() == 1 && d->wraps->lineInfo(0).segs[0].tabStop < 0) { if(lineAlign.testFlag(AlignRight)) {