Improve BrowserView to show all links in .md files

This commit is contained in:
David Capello 2016-12-06 21:30:46 -03:00
parent fec4e27d8e
commit 12726fedf2
3 changed files with 251 additions and 45 deletions

View File

@ -31,6 +31,7 @@
<color id="desktop" value="#968275" /> <color id="desktop" value="#968275" />
<color id="textbox_text" value="#000000" /> <color id="textbox_text" value="#000000" />
<color id="textbox_face" value="#ffffff" /> <color id="textbox_face" value="#ffffff" />
<color id="textbox_code_face" value="#eeeeee" />
<color id="entry_suffix" value="#c6c6c6" /> <color id="entry_suffix" value="#c6c6c6" />
<color id="link_text" value="#2c4c91" /> <color id="link_text" value="#2c4c91" />
<color id="link_hover" value="#ff5555" /> <color id="link_hover" value="#ff5555" />

View File

@ -16,23 +16,45 @@
#include "app/ui/workspace.h" #include "app/ui/workspace.h"
#include "base/file_handle.h" #include "base/file_handle.h"
#include "base/fs.h" #include "base/fs.h"
#include "ui/box.h" #include "base/split_string.h"
#include "she/font.h"
#include "ui/link_label.h" #include "ui/link_label.h"
#include "ui/menu.h" #include "ui/menu.h"
#include "ui/message.h" #include "ui/message.h"
#include "ui/paint_event.h"
#include "ui/resize_event.h"
#include "ui/size_hint_event.h"
#include "ui/system.h" #include "ui/system.h"
#include "ui/textbox.h" #include "ui/textbox.h"
#include "cmark.h" #include "cmark.h"
#include <array> #include <array>
#include <cstring>
#include <string>
#include <vector>
namespace app { namespace app {
using namespace ui; using namespace ui;
using namespace app::skin; using namespace app::skin;
class BrowserView::CMarkBox : public ui::VBox { // TODO This is not the best implementation, but it's "good enough"
// for a first version.
class BrowserView::CMarkBox : public Widget {
class Break : public Widget {
public:
Break() {
setMinSize(gfx::Size(0, font()->height()));
}
};
class OpenList : public Widget { };
class CloseList : public Widget { };
class Item : public Label {
public:
Item(const std::string& text) : Label(text) { }
};
public: public:
CMarkBox() { CMarkBox() {
setBgColor(SkinTheme::instance()->colors.textboxFace()); setBgColor(SkinTheme::instance()->colors.textboxFace());
@ -69,7 +91,7 @@ public:
} }
else { else {
clear(); clear();
addTextBox("File not found: " + file); addText("File not found: " + file);
} }
cmark_parser_free(parser); cmark_parser_free(parser);
@ -77,6 +99,90 @@ public:
} }
private: private:
void layoutElements(int width,
std::function<void(const gfx::Rect& bounds,
Widget* child)> callback) {
const WidgetsList& children = this->children();
const gfx::Rect cpos = childrenBounds();
gfx::Point p = cpos.origin();
int maxH = 0;
int itemLevel = 0;
Widget* prevChild = nullptr;
for (auto child : children) {
gfx::Size sz = child->sizeHint(gfx::Size(width, 0));
bool isBreak = (dynamic_cast<Break*>(child) ? true: false);
bool isOpenList = (dynamic_cast<OpenList*>(child) ? true: false);
bool isCloseList = (dynamic_cast<CloseList*>(child) ? true: false);
bool isItem = (dynamic_cast<Item*>(child) ? true: false);
if (isOpenList) {
++itemLevel;
}
else if (isCloseList) {
--itemLevel;
}
else if (isItem) {
p.x -= sz.w;
}
if (child->isExpansive() ||
p.x+sz.w > cpos.x+width ||
isBreak || isOpenList || isCloseList) {
p.x = cpos.x + itemLevel*font()->textLength(" - ");
p.y += maxH;
maxH = 0;
prevChild = nullptr;
}
if (child->isExpansive())
sz.w = std::max(sz.w, width);
callback(gfx::Rect(p, sz), child);
if (!isItem) prevChild = child;
if (isBreak) prevChild = nullptr;
maxH = std::max(maxH, sz.h);
p.x += sz.w;
}
}
void onSizeHint(SizeHintEvent& ev) override {
gfx::Size sz;
layoutElements(
View::getView(this)->viewportBounds().w - border().width(),
[&](const gfx::Rect& rc, Widget* child) {
sz.w = std::max(sz.w, rc.x+rc.w-this->bounds().x);
sz.h = std::max(sz.h, rc.y+rc.h-this->bounds().y);
});
sz.w += border().right();
sz.h += border().bottom();
ev.setSizeHint(sz);
}
void onResize(ResizeEvent& ev) override {
setBoundsQuietly(ev.bounds());
layoutElements(
View::getView(this)->viewportBounds().w - border().width(),
[](const gfx::Rect& rc, Widget* child) {
child->setBounds(rc);
});
}
void onPaint(PaintEvent& ev) override {
Graphics* g = ev.graphics();
gfx::Rect rc = clientBounds();
auto skin = (SkinTheme*)theme();
g->fillRect(skin->colors.textboxFace(), rc);
}
bool onProcessMessage(Message* msg) override { bool onProcessMessage(Message* msg) override {
switch (msg->type()) { switch (msg->type()) {
@ -97,7 +203,7 @@ private:
} }
} }
return VBox::onProcessMessage(msg); return Widget::onProcessMessage(msg);
} }
void clear() { void clear() {
@ -109,10 +215,10 @@ private:
void processNode(cmark_node* root) { void processNode(cmark_node* root) {
clear(); clear();
std::string content; m_content.clear();
bool inHeading = false; bool inHeading = false;
bool inImage = false; bool inImage = false;
bool extraSeparation = true;
const char* inLink = nullptr; const char* inLink = nullptr;
cmark_iter* iter = cmark_iter_new(root); cmark_iter* iter = cmark_iter_new(root);
@ -122,44 +228,92 @@ private:
switch (cmark_node_get_type(cur)) { switch (cmark_node_get_type(cur)) {
case CMARK_NODE_CODE_BLOCK:
case CMARK_NODE_TEXT: { case CMARK_NODE_TEXT: {
const char* text = cmark_node_get_literal(cur); const char* text = cmark_node_get_literal(cur);
if (!inImage && text) { if (!inImage && text) {
if (inLink && inHeading) { if (inLink) {
closeContent(content); if (!m_content.empty() &&
addLink(inLink, text); m_content[m_content.size()-1] != ' ') {
extraSeparation = false; m_content += " ";
}
m_content += text;
} }
else { else {
if (inLink && !content.empty() && m_content += text;
content[content.size()-1] != ' ') { if (inHeading)
content += " "; closeContent();
}
content += text;
} }
} }
break; break;
} }
case CMARK_NODE_INLINE_HTML: {
const char* text = cmark_node_get_literal(cur);
if (text && std::strncmp(text, "<br />", 6) == 0) {
closeContent();
addBreak();
}
break;
}
case CMARK_NODE_CODE: {
const char* text = cmark_node_get_literal(cur);
if (text) {
closeContent();
addCodeInline(text);
}
break;
}
case CMARK_NODE_CODE_BLOCK: {
const char* text = cmark_node_get_literal(cur);
if (text) {
closeContent();
addCodeBlock(text);
}
break;
}
case CMARK_NODE_SOFTBREAK: {
m_content += " ";
break;
}
case CMARK_NODE_LINEBREAK: { case CMARK_NODE_LINEBREAK: {
content += "\n"; closeContent();
addBreak();
break;
}
case CMARK_NODE_LIST: {
if (ev_type == CMARK_EVENT_ENTER) {
closeContent();
addChild(new OpenList);
}
else if (ev_type == CMARK_EVENT_EXIT) {
closeContent();
addChild(new CloseList);
}
break; break;
} }
case CMARK_NODE_ITEM: { case CMARK_NODE_ITEM: {
if (ev_type == CMARK_EVENT_ENTER) if (ev_type == CMARK_EVENT_ENTER) {
content += " - "; closeContent();
addChild(new Item(" - "));
}
break; break;
} }
case CMARK_NODE_THEMATIC_BREAK: { case CMARK_NODE_THEMATIC_BREAK: {
if (ev_type == CMARK_EVENT_ENTER) { if (ev_type == CMARK_EVENT_ENTER) {
closeContent(content); closeContent();
addSeparator(); addSeparator();
} }
else if (ev_type == CMARK_EVENT_EXIT) { else if (ev_type == CMARK_EVENT_EXIT) {
content += "\n\n"; closeContent();
addBreak();
addBreak();
} }
break; break;
} }
@ -168,25 +322,24 @@ private:
if (ev_type == CMARK_EVENT_ENTER) { if (ev_type == CMARK_EVENT_ENTER) {
inHeading = true; inHeading = true;
closeContent(content); closeContent();
addSeparator(); addSeparator();
} }
else if (ev_type == CMARK_EVENT_EXIT) { else if (ev_type == CMARK_EVENT_EXIT) {
inHeading = false; inHeading = false;
content += "\n"; closeContent();
if (extraSeparation) addBreak();
content += "\n"; addBreak();
else
extraSeparation = true;
} }
break; break;
} }
case CMARK_NODE_LIST:
case CMARK_NODE_PARAGRAPH: { case CMARK_NODE_PARAGRAPH: {
if (ev_type == CMARK_EVENT_EXIT) if (ev_type == CMARK_EVENT_EXIT) {
content += "\n"; closeContent();
addBreak();
}
break; break;
} }
@ -200,8 +353,16 @@ private:
case CMARK_NODE_LINK: { case CMARK_NODE_LINK: {
if (ev_type == CMARK_EVENT_ENTER) { if (ev_type == CMARK_EVENT_ENTER) {
inLink = cmark_node_get_url(cur); inLink = cmark_node_get_url(cur);
if (inLink)
closeContent();
} }
else if (ev_type == CMARK_EVENT_EXIT) { else if (ev_type == CMARK_EVENT_EXIT) {
if (inLink) {
if (!m_content.empty()) {
addLink(inLink, m_content);
m_content.clear();
}
}
inLink = nullptr; inLink = nullptr;
} }
} }
@ -210,28 +371,68 @@ private:
} }
cmark_iter_free(iter); cmark_iter_free(iter);
closeContent(content); closeContent();
} }
void closeContent(std::string& content) { void closeContent() {
if (!content.empty()) { if (!m_content.empty()) {
addTextBox(content); addText(m_content);
content.clear(); m_content.clear();
} }
} }
void addSeparator() { void addSeparator() {
auto sep = new Separator("", HORIZONTAL); auto sep = new Separator("", HORIZONTAL);
sep->setBorder(gfx::Border(0, font()->height(), 0, font()->height()));
sep->setBgColor(SkinTheme::instance()->colors.textboxFace()); sep->setBgColor(SkinTheme::instance()->colors.textboxFace());
sep->setExpansive(true);
addChild(sep); addChild(sep);
} }
void addTextBox(const std::string& content) { void addBreak() {
addChild(new TextBox(content, LEFT | WORDWRAP)); addChild(new Break);
}
void addText(const std::string& content) {
std::vector<std::string> words;
base::split_string(content, words, " ");
for (const auto& word : words)
if (!word.empty()) {
Label* label;
if (word.size() > 4 &&
std::strncmp(word.c_str(), "http", 4) == 0)
label = new LinkLabel(word);
else
label = new Label(word);
// Uncomment this line to debug labels
//label->setBgColor(gfx::rgba((rand()%128)+128, 128, 128));
addChild(label);
}
}
void addCodeInline(const std::string& content) {
auto label = new Label(content);
label->setBgColor(SkinTheme::instance()->colors.textboxCodeFace());
addChild(label);
}
void addCodeBlock(const std::string& content) {
auto textBox = new TextBox(content, LEFT);
textBox->setBorder(gfx::Border(4*guiscale()));
textBox->setBgColor(SkinTheme::instance()->colors.textboxCodeFace());
addChild(textBox);
} }
void addLink(const std::string& url, const std::string& text) { void addLink(const std::string& url, const std::string& text) {
addChild(new LinkLabel(url, text)); auto label = new LinkLabel(url, text);
// Uncomment this line to debug labels
//label->setBgColor(gfx::rgba((rand()%128)+128, 128, 128));
addChild(label);
} }
void relayout() { void relayout() {
@ -241,10 +442,10 @@ private:
view->updateView(); view->updateView();
view->setViewScroll(gfx::Point(0, 0)); view->setViewScroll(gfx::Point(0, 0));
} }
invalidate(); invalidate();
} }
std::string m_content;
}; };
BrowserView::BrowserView() BrowserView::BrowserView()

View File

@ -686,8 +686,9 @@ void SkinTheme::initWidget(Widget* widget)
break; break;
case kTextBoxWidget: case kTextBoxWidget:
BORDER(0); BORDER(4*guiscale());
widget->setChildSpacing(0); widget->setChildSpacing(0);
widget->setBgColor(colors.textboxFace());
break; break;
case kViewWidget: case kViewWidget:
@ -1042,20 +1043,24 @@ void SkinTheme::paintLinkLabel(PaintEvent& ev)
Graphics* g = ev.graphics(); Graphics* g = ev.graphics();
Widget* widget = static_cast<Widget*>(ev.getSource()); Widget* widget = static_cast<Widget*>(ev.getSource());
Style* style = styles.link(); Style* style = styles.link();
gfx::Rect bounds = widget->clientBounds(); Rect text, rc = widget->clientBounds();
gfx::Color bg = BGCOLOR; gfx::Color bg = BGCOLOR;
SkinStylePropertyPtr styleProp = widget->getProperty(SkinStyleProperty::Name); SkinStylePropertyPtr styleProp = widget->getProperty(SkinStyleProperty::Name);
if (styleProp) if (styleProp)
style = styleProp->getStyle(); style = styleProp->getStyle();
if (!is_transparent(bg))
g->fillRect(bg, rc);
rc.shrink(widget->border());
Style::State state; Style::State state;
if (widget->hasMouseOver()) state += Style::hover(); if (widget->hasMouseOver()) state += Style::hover();
if (widget->isSelected()) state += Style::clicked(); if (widget->isSelected()) state += Style::clicked();
if (!widget->isEnabled()) state += Style::disabled(); if (!widget->isEnabled()) state += Style::disabled();
g->fillRect(bg, bounds); widget->getTextIconInfo(nullptr, &text);
style->paint(g, bounds, widget->text().c_str(), state); style->paint(g, text, widget->text().c_str(), state);
} }
void SkinTheme::paintListBox(PaintEvent& ev) void SkinTheme::paintListBox(PaintEvent& ev)
@ -1528,8 +1533,7 @@ void SkinTheme::paintTextBox(ui::PaintEvent& ev)
Widget* widget = static_cast<Widget*>(ev.getSource()); Widget* widget = static_cast<Widget*>(ev.getSource());
drawTextBox(g, widget, NULL, NULL, drawTextBox(g, widget, NULL, NULL,
colors.textboxFace(), BGCOLOR, colors.textboxText());
colors.textboxText());
} }
void SkinTheme::paintView(PaintEvent& ev) void SkinTheme::paintView(PaintEvent& ev)