diff --git a/include/lgi/common/LiteHtmlView.h b/include/lgi/common/LiteHtmlView.h new file mode 100644 --- /dev/null +++ b/include/lgi/common/LiteHtmlView.h @@ -0,0 +1,22 @@ +#pragma once + +class LiteHtmlView : + public LLayout +{ +protected: + struct LiteHtmlViewPriv *d; + +public: + LiteHtmlView(int id); + ~LiteHtmlView(); + + bool SetUrl(LString url); + + // LLayout impl + void OnAttach() override; + LCursor GetCursor(int x, int y) override; + void OnPaint(LSurface *pDC) override; + int OnNotify(LViewI *c, LNotification n) override; + void OnMouseClick(LMouse &m) override; + void OnMouseMove(LMouse &m) override; +}; diff --git a/src/common/LiteHtml/LiteHtmlView.cpp b/src/common/LiteHtml/LiteHtmlView.cpp new file mode 100644 --- /dev/null +++ b/src/common/LiteHtml/LiteHtmlView.cpp @@ -0,0 +1,416 @@ + +#include "lgi/common/Lgi.h" +#include "lgi/common/Box.h" +#include "lgi/common/Edit.h" +#include "lgi/common/DisplayString.h" +#include "lgi/common/Layout.h" +#include "lgi/common/ScrollBar.h" +#include "lgi/common/Menu.h" +#include "lgi/common/LiteHtmlView.h" + +#undef min +#undef max +#include "litehtml/html.h" + +#define NOT_IMPL \ + printf("%s:%i - %s not impl.\n", _FL, __func__); \ + LAssert(!"not impl"); + + +struct LiteHtmlViewPriv : + public litehtml::document_container +{ + LiteHtmlView *view = NULL; + LWindow *wnd = NULL; + litehtml::document::ptr doc; + LRect client; + LString cursorName; + LHashTbl, LFont*> fontMap; + + LiteHtmlViewPriv(LiteHtmlView *v) : view(v) + { + + } + + ~LiteHtmlViewPriv() + { + // Do this before releasing other owned objects, like the fontMap. + doc.reset(); + } + + LColour Convert(const litehtml::web_color &c) + { + return LColour(c.red, c.green, c.blue, c.alpha); + } + + LRect Convert(const litehtml::position &p) + { + LRect r(p.x, p.y, p.x + p.width - 1, p.y + p.height - 1); + return r; + } + + void UpdateScreen(litehtml::position::vector &redraw) + { + if (redraw.size() > 0) + { + // FIXME: should invalidate just the dirty regions... + view->Invalidate(); + } + } + + + litehtml::uint_ptr create_font( const char* faceName, + int size, + int weight, + litehtml::font_style italic, + unsigned int decoration, + litehtml::font_metrics *fm) + { + litehtml::uint_ptr hnd; + do + { + hnd = LRand(10000); + } + while (fontMap.Find(hnd) != NULL); + + LFont *fnt = new LFont; + bool status = fnt->Create(faceName, LCss::Len(LCss::LenPt, size) ); + if (!status) + LgiTrace("%s:%i - failed to create font(%s,%i)\n", _FL, faceName, size); + fontMap.Add(hnd, fnt); + + if (fm) + { + LDisplayString ds(fnt, "x"); + fm->height = fnt->GetHeight(); + fm->ascent = fnt->Ascent(); + fm->descent = fnt->Descent(); + fm->x_height = ds.Y(); + fm->draw_spaces = false; + } + + return hnd; + } + + void delete_font(litehtml::uint_ptr hFont) + { + auto fnt = fontMap.Find(hFont); + if (fnt) + { + delete fnt; + fontMap.Delete(hFont); + } + } + + int text_width(const char* text, litehtml::uint_ptr hFont) + { + auto fnt = fontMap.Find(hFont); + if (!fnt) + return 0; + + LDisplayString ds(fnt, text); + return ds.X(); + } + + void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) + { + auto pDC = (LSurface*)hdc; + auto Fnt = fontMap.Find(hFont); + if (!pDC || !Fnt) + return; + + LDisplayString ds(Fnt, text); + Fnt->Fore(Convert(color)); + Fnt->Transparent(true); + ds.Draw(pDC, pos.x, pos.y); + } + + int pt_to_px(int pt) const + { + auto dpi = wnd->GetDpi(); + int px = pt * dpi.x / 72; + return px; + } + + int get_default_font_size() const + { + return 13; // LSysFont->PointSize(); + } + + const char *get_default_font_name() const + { + return LSysFont->Face(); + } + + void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) + { + auto pDC = (LSurface*)hdc; + auto Fnt = fontMap.Find(marker.font); + if (!pDC) + return; + + pDC->Colour(Convert(marker.color)); + switch (marker.marker_type) + { + case litehtml::list_style_type_disc: + { + pDC->FilledCircle(marker.pos.x, marker.pos.y, 3); + break; + } + default: + { + NOT_IMPL; + break; + } + } + } + + void load_image(const char *src, const char *baseurl, bool redraw_on_ready) + { + NOT_IMPL + } + + void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) + { + NOT_IMPL + } + + void draw_background(litehtml::uint_ptr hdc, const std::vector &background) + { + auto pDC = (LSurface*)hdc; + for (auto b: background) + { + pDC->Colour(Convert(b.color)); + auto rc = Convert(b.border_box); + pDC->Rectangle(&rc); + } + } + + void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) + { + auto pDC = (LSurface*)hdc; + auto drawEdge = [&](const litehtml::border &b, int x, int y, int dx, int dy, int ix, int iy) + { + pDC->Colour(Convert(b.color)); + for (int i=0; iLine(x, y, x+dx, y+dy); + x += ix; + y += iy; + } + }; + + int x2 = draw_pos.width - 1; + int y2 = draw_pos.height - 1; + drawEdge(borders.left, draw_pos.x, draw_pos.y, 0, y2, 1, 0); + drawEdge(borders.top, draw_pos.x, draw_pos.y, x2, 0, 0, 1); + drawEdge(borders.right, draw_pos.x+x2, draw_pos.y, 0, y2, -1, 0); + drawEdge(borders.bottom, draw_pos.x, draw_pos.y+y2, x2, 0, 0, -1); + } + + void set_caption(const char* caption) + { + wnd->Name(caption); + } + + void set_base_url(const char* base_url) + { + NOT_IMPL + } + + void link(const std::shared_ptr& doc, const litehtml::element::ptr& el) + { + NOT_IMPL + } + + void on_anchor_click(const char* url, const litehtml::element::ptr& el) + { + NOT_IMPL + } + + void set_cursor(const char* cursor) + { + cursorName = cursor; + } + + void transform_text(litehtml::string& text, litehtml::text_transform tt) + { + NOT_IMPL + } + + void import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl) + { + NOT_IMPL + } + + void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) + { + NOT_IMPL + } + + void del_clip() + { + NOT_IMPL + } + + void get_client_rect(litehtml::position &out) const + { + out = litehtml::position(client.x1, client.y1, client.X(), client.Y()); + } + + litehtml::element::ptr create_element( const char* tag_name, + const litehtml::string_map& attributes, + const std::shared_ptr& doc) + { + return NULL; + } + + void get_media_features(litehtml::media_features& media) const + { + // NOT_IMPL + } + + void get_language(litehtml::string& language, litehtml::string& culture) const + { + NOT_IMPL + } + + /* + litehtml::string resolve_color(const litehtml::string &color) const + { + NOT_IMPL + return litehtml::string(); + } + */ +}; + +///////////////////////////////////////////////////////////////// +LiteHtmlView::LiteHtmlView(int id) +{ + d = new LiteHtmlViewPriv(this); + SetId(id); +} + +LiteHtmlView::~LiteHtmlView() +{ + delete d; +} + +void LiteHtmlView::OnAttach() +{ + d->wnd = GetWindow(); +} + +LCursor LiteHtmlView::GetCursor(int x, int y) +{ + if (d->cursorName == "pointer") + return LCUR_PointingHand; + + return LCUR_Normal; +} + +bool LiteHtmlView::SetUrl(LString url) +{ + if (LFileExists(url)) + { + auto html_text = LReadFile(url); + if (html_text) + { + d->doc = litehtml::document::createFromString(html_text, d); + Invalidate(); + return d->doc != NULL; + } + } + + return false; +} + +void LiteHtmlView::OnPaint(LSurface *pDC) +{ + #ifdef WINDOWS + LDoubleBuffer buf(pDC); + #endif + + d->client = GetClient(); + if (d->doc) + { + pDC->Colour(L_WORKSPACE); + pDC->Rectangle(); + + auto width = pDC->X(); + int r = d->doc->render(width); + if (r) + { + auto width = d->doc->content_width(); + auto height = d->doc->content_height(); + if (height > Y()) + { + SetScrollBars(false, true); + if (VScroll) + { + VScroll->SetRange(height); + VScroll->SetPage(Y()); + } + } + + litehtml::position clip(0, 0, pDC->X(), pDC->Y()); + d->doc->draw((litehtml::uint_ptr)pDC, 0, VScroll?-VScroll->Value():0, &clip); + } + } + else + { + LLayout::OnPaint(pDC); + } +} + +int LiteHtmlView::OnNotify(LViewI *c, LNotification n) +{ + // printf("OnNotify %i=%i, %i=%i\n", c->GetId(), IDC_VSCROLL, n.Type, LNotifyValueChanged); + if (c->GetId() == IDC_VSCROLL && + n.Type == LNotifyValueChanged) + { + // printf("Inval\n"); + Invalidate(); + } + + return LLayout::OnNotify(c, n); +} + +void LiteHtmlView::OnMouseClick(LMouse &m) +{ + if (!d->doc) + return; + + int64_t sx, sy; + GetScrollPos(sx, sy); + litehtml::position::vector redraw_boxes; + + if (m.IsContextMenu()) + { + LSubMenu sub; + sub.AppendItem("notImpl: submenu", -1, false); + sub.Float(this, m); + } + else if (m.Left()) + { + if (m.Down()) + d->doc->on_lbutton_down(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); + else + d->doc->on_lbutton_up(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); + } + + d->UpdateScreen(redraw_boxes); +} + +void LiteHtmlView::OnMouseMove(LMouse &m) +{ + if (!d->doc) + return; + + int64_t sx, sy; + GetScrollPos(sx, sy); + litehtml::position::vector redraw_boxes; + d->doc->on_mouse_over(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); + + d->UpdateScreen(redraw_boxes); +} diff --git a/test/LiteHtml/CMakeLists.txt b/test/LiteHtml/CMakeLists.txt --- a/test/LiteHtml/CMakeLists.txt +++ b/test/LiteHtml/CMakeLists.txt @@ -1,20 +1,21 @@ cmake_minimum_required(VERSION 3.18) project(LgiLiteHtml) set(LGI_LIB_ONLY TRUE) add_subdirectory(../.. lgi) add_subdirectory(../../../../../codelib/litehtml litehtml) set(SRC src/main.cpp + ../../src/common/LiteHtml/LiteHtmlView.cpp ../../src/common/Lgi/LgiMain.cpp) if(WIN32) set(APP_TYPE WIN32) else() set(APP_TYPE) endif() add_executable(LgiLiteHtml ${APP_TYPE} ${SRC}) target_link_libraries(LgiLiteHtml PUBLIC lgi litehtml) \ No newline at end of file diff --git a/test/LiteHtml/src/main.cpp b/test/LiteHtml/src/main.cpp --- a/test/LiteHtml/src/main.cpp +++ b/test/LiteHtml/src/main.cpp @@ -1,450 +1,69 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/Edit.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Layout.h" #include "lgi/common/ScrollBar.h" - -#undef min -#undef max -#include "litehtml/html.h" +#include "lgi/common/Menu.h" +#include "lgi/common/LiteHtmlView.h" const char *AppName = "LgiLiteHtml"; enum Ctrls { IDC_BOX = 100, IDC_LOCATION, IDC_BROWSER, }; -#define NOT_IMPL \ - printf("%s:%i - %s not impl.\n", _FL, __func__); \ - LAssert(!"not impl"); - -class HtmlView : - public LLayout, - public litehtml::document_container -{ - litehtml::document::ptr doc; - -protected: - LWindow *wnd = NULL; - LRect client; - LString cursorName; - LHashTbl, LFont*> fontMap; - - LColour Convert(const litehtml::web_color &c) - { - return LColour(c.red, c.green, c.blue, c.alpha); - } - - LRect Convert(const litehtml::position &p) - { - LRect r(p.x, p.y, p.x + p.width - 1, p.y + p.height - 1); - return r; - } - - litehtml::uint_ptr create_font( const char* faceName, - int size, - int weight, - litehtml::font_style italic, - unsigned int decoration, - litehtml::font_metrics *fm) - { - litehtml::uint_ptr hnd; - do - { - hnd = LRand(10000); - } - while (fontMap.Find(hnd) != NULL); - - LFont *fnt = new LFont; - bool status = fnt->Create(faceName, LCss::Len(LCss::LenPt, size) ); - if (!status) - LgiTrace("%s:%i - failed to create font(%s,%i)\n", _FL, faceName, size); - fontMap.Add(hnd, fnt); - - if (fm) - { - LDisplayString ds(fnt, "x"); - fm->height = fnt->GetHeight(); - fm->ascent = fnt->Ascent(); - fm->descent = fnt->Descent(); - fm->x_height = ds.Y(); - fm->draw_spaces = false; - } - - return hnd; - } - - void delete_font(litehtml::uint_ptr hFont) - { - auto fnt = fontMap.Find(hFont); - if (fnt) - { - delete fnt; - fontMap.Delete(hFont); - } - } - - int text_width(const char* text, litehtml::uint_ptr hFont) - { - auto fnt = fontMap.Find(hFont); - if (!fnt) - return 0; - - LDisplayString ds(fnt, text); - return ds.X(); - } - - void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) - { - auto pDC = (LSurface*)hdc; - auto Fnt = fontMap.Find(hFont); - if (!pDC || !Fnt) - return; - - LDisplayString ds(Fnt, text); - Fnt->Fore(Convert(color)); - Fnt->Transparent(true); - ds.Draw(pDC, pos.x, pos.y); - } - - int pt_to_px(int pt) const - { - auto dpi = wnd->GetDpi(); - int px = pt * dpi.x / 72; - return px; - } - - int get_default_font_size() const - { - return 13; // LSysFont->PointSize(); - } - - const char *get_default_font_name() const - { - return LSysFont->Face(); - } - - void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) - { - auto pDC = (LSurface*)hdc; - auto Fnt = fontMap.Find(marker.font); - if (!pDC) - return; - - pDC->Colour(Convert(marker.color)); - switch (marker.marker_type) - { - case litehtml::list_style_type_disc: - { - pDC->FilledCircle(marker.pos.x, marker.pos.y, 3); - break; - } - default: - { - NOT_IMPL; - break; - } - } - } - - void load_image(const char *src, const char *baseurl, bool redraw_on_ready) - { - NOT_IMPL - } - - void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) - { - NOT_IMPL - } - - void draw_background(litehtml::uint_ptr hdc, const std::vector &background) - { - auto pDC = (LSurface*)hdc; - for (auto b: background) - { - pDC->Colour(Convert(b.color)); - auto rc = Convert(b.border_box); - pDC->Rectangle(&rc); - } - } - - void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) - { - auto pDC = (LSurface*)hdc; - auto drawEdge = [&](const litehtml::border &b, int x, int y, int dx, int dy, int ix, int iy) - { - pDC->Colour(Convert(b.color)); - for (int i=0; iLine(x, y, x+dx, y+dy); - x += ix; - y += iy; - } - }; - - int x2 = draw_pos.width - 1; - int y2 = draw_pos.height - 1; - drawEdge(borders.left, draw_pos.x, draw_pos.y, 0, y2, 1, 0); - drawEdge(borders.top, draw_pos.x, draw_pos.y, x2, 0, 0, 1); - drawEdge(borders.right, draw_pos.x+x2, draw_pos.y, 0, y2, -1, 0); - drawEdge(borders.bottom, draw_pos.x, draw_pos.y+y2, x2, 0, 0, -1); - } - - void set_caption(const char* caption) - { - wnd->Name(caption); - } - - void set_base_url(const char* base_url) - { - NOT_IMPL - } - - void link(const std::shared_ptr& doc, const litehtml::element::ptr& el) - { - NOT_IMPL - } - - void on_anchor_click(const char* url, const litehtml::element::ptr& el) - { - NOT_IMPL - } - - void set_cursor(const char* cursor) - { - cursorName = cursor; - } - - void transform_text(litehtml::string& text, litehtml::text_transform tt) - { - NOT_IMPL - } - - void import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl) - { - NOT_IMPL - } - - void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) - { - NOT_IMPL - } - - void del_clip() - { - NOT_IMPL - } - - void get_client_rect(litehtml::position &out) const - { - out = litehtml::position(client.x1, client.y1, client.X(), client.Y()); - } - - litehtml::element::ptr create_element( const char* tag_name, - const litehtml::string_map& attributes, - const std::shared_ptr& doc) - { - return NULL; - } - - void get_media_features(litehtml::media_features& media) const - { - // NOT_IMPL - } - - void get_language(litehtml::string& language, litehtml::string& culture) const - { - NOT_IMPL - } - - /* - litehtml::string resolve_color(const litehtml::string &color) const - { - NOT_IMPL - return litehtml::string(); - } - */ - -public: - HtmlView(int id) - { - SetId(id); - } - - ~HtmlView() - { - // Do this before releasing other owned objects, like the fontMap. - doc.reset(); - } - - void OnAttach() - { - wnd = GetWindow(); - } - - LCursor GetCursor(int x, int y) override - { - if (cursorName == "pointer") - return LCUR_PointingHand; - - return LCUR_Normal; - } - - bool SetUrl(LString url) - { - if (LFileExists(url)) - { - auto html_text = LReadFile(url); - if (html_text) - { - doc = litehtml::document::createFromString(html_text, this); - Invalidate(); - return doc != NULL; - } - } - - return false; - } - - void OnPaint(LSurface *pDC) - { - #ifdef WINDOWS - LDoubleBuffer buf(pDC); - #endif - - client = GetClient(); - if (doc) - { - pDC->Colour(L_WORKSPACE); - pDC->Rectangle(); - - auto width = pDC->X(); - int r = doc->render(width); - if (r) - { - auto width = doc->content_width(); - auto height = doc->content_height(); - if (height > Y()) - { - SetScrollBars(false, true); - if (VScroll) - { - VScroll->SetRange(height); - VScroll->SetPage(Y()); - } - } - - litehtml::position clip(0, 0, pDC->X(), pDC->Y()); - doc->draw((litehtml::uint_ptr)pDC, 0, VScroll?-VScroll->Value():0, &clip); - } - } - else - { - LLayout::OnPaint(pDC); - } - } - - int OnNotify(LViewI *c, LNotification n) - { - // printf("OnNotify %i=%i, %i=%i\n", c->GetId(), IDC_VSCROLL, n.Type, LNotifyValueChanged); - if (c->GetId() == IDC_VSCROLL && - n.Type == LNotifyValueChanged) - { - // printf("Inval\n"); - Invalidate(); - } - - return LLayout::OnNotify(c, n); - } - - /* - bool on_mouse_over(int x, int y, int client_x, int client_y, position::vector& redraw_boxes); - bool on_lbutton_down(int x, int y, int client_x, int client_y, position::vector& redraw_boxes); - bool on_lbutton_up(int x, int y, int client_x, int client_y, position::vector& redraw_boxes); - bool on_mouse_leave(position::vector& redraw_boxes); - */ - - void OnMouseClick(LMouse &m) - { - if (!doc) - return; - - int64_t sx, sy; - GetScrollPos(sx, sy); - litehtml::position::vector redraw_boxes; - if (m.Left()) - { - if (m.Down()) - doc->on_lbutton_down(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); - else - doc->on_lbutton_up(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); - } - } - - void OnMouseMove(LMouse &m) - { - if (!doc) - return; - - int64_t sx, sy; - GetScrollPos(sx, sy); - litehtml::position::vector redraw_boxes; - doc->on_mouse_over(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); - } -}; - class App : public LWindow { LBox *box = NULL; LEdit *location = NULL; - HtmlView *browser = NULL; + LiteHtmlView *browser = NULL; public: App() { LRect r(200, 200, 1400, 1000); SetPos(r); Name(AppName); MoveToCenter(); SetQuitOnClose(true); if (Attach(0)) { AddView(box = new LBox(IDC_BOX, true)); box->AddView(location = new LEdit(IDC_LOCATION, 0, 0, 100, 20)); - box->AddView(browser = new HtmlView(IDC_BROWSER)); + box->AddView(browser = new LiteHtmlView(IDC_BROWSER)); box->Value(LSysFont->GetHeight() + 8); AttachChildren(); location->Focus(true); Visible(true); } } void SetUrl(LString s) { if (location) location->Name(s); if (browser) browser->SetUrl(s); } void OnReceiveFiles(LArray &Files) { SetUrl(Files[0]); } }; int LgiMain(OsAppArguments &AppArgs) { LApp app(AppArgs, "application/x-lgi-litehtml"); if (app.IsOk()) { app.AppWnd = new App; app.Run(); } return 0; }