mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-10 01:13:49 +00:00
Improve BrowserView to show all links in .md files
This commit is contained in:
parent
fec4e27d8e
commit
12726fedf2
@ -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" />
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user