genode/repos/demo/src/app/scout/browser_window.cc

481 lines
12 KiB
C++

/*
* \brief Browser window implementation
* \date 2005-10-24
* \author Norman Feske <norman.feske@genode-labs.com>
*
* This class defines the layout and user policy of a browser window.
*/
/*
* Copyright (C) 2005-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#include <scout_gfx/random.h>
#include <scout/misc_math.h>
#include "browser_window.h"
using namespace Scout;
/****************************
** External graphics data **
****************************/
#define IOR_MAP _binary_ior_map_start
#define HOME_RGBA _binary_home_rgba_start
#define COVER_RGBA _binary_cover_rgba_start
#define INDEX_RGBA _binary_index_rgba_start
#define ABOUT_RGBA _binary_about_rgba_start
#define FORWARD_RGBA _binary_forward_rgba_start
#define BACKWARD_RGBA _binary_backward_rgba_start
#define SIZER_RGBA _binary_sizer_rgba_start
#define TITLEBAR_RGBA _binary_titlebar_rgba_start
extern short IOR_MAP[];
extern unsigned char HOME_RGBA[];
extern unsigned char COVER_RGBA[];
extern unsigned char INDEX_RGBA[];
extern unsigned char ABOUT_RGBA[];
extern unsigned char FORWARD_RGBA[];
extern unsigned char BACKWARD_RGBA[];
extern unsigned char SIZER_RGBA[];
extern unsigned char TITLEBAR_RGBA[];
enum {
ICON_HOME = 0,
ICON_BACKWARD = 1,
ICON_FORWARD = 2,
ICON_INDEX = 3,
ICON_ABOUT = 4,
NUM_ICONS = 5
};
/* icon graphics data */
static unsigned char *glow_icon_gfx[] = {
HOME_RGBA,
BACKWARD_RGBA,
FORWARD_RGBA,
INDEX_RGBA,
ABOUT_RGBA,
};
/* color definitions for glowing effect of the icons */
static Color glow_icon_col[] = {
Color(210, 210, 0),
Color( 0, 0, 160),
Color( 0, 0, 160),
Color( 0, 160, 0),
Color(160, 0, 0),
};
/***************
** Utilities **
***************/
/**
* Transform rgba source image to image with native pixel type
*
* If we specify an empty buffer as alpha channel (all values zero), we simply
* assign the source image data to the destination buffer. If there are valid
* values in the destination alpha channel, we paint the source image on top
* of the already present image data. This enables us to combine multiple
* rgba buffers (layers) into one destination buffer.
*/
template <typename PT>
static void extract_rgba(const unsigned char *src, int w, int h,
PT *dst_pixel, unsigned char *dst_alpha)
{
for (int i = 0; i < w*h; i++, src += 4) {
int r = src[0];
int g = src[1];
int b = src[2];
int a = src[3];
if (dst_alpha[i]) {
PT s(r, g, b);
dst_pixel[i] = PT::mix(dst_pixel[i], s, a);
dst_alpha[i] = max((int)dst_alpha[i], a);
} else {
dst_pixel[i].rgba(r, g, b);
dst_alpha[i] = a;
}
}
}
/********************
** Event handlers **
********************/
class Iconbar_event_handler : public Scout::Event_handler
{
private:
/*
* Noncopyable
*/
Iconbar_event_handler(Iconbar_event_handler const &);
Iconbar_event_handler &operator = (Iconbar_event_handler const &);
Fader *_fader;
Browser *_browser;
int _icon_id;
public:
/**
* Constructor
*/
Iconbar_event_handler(Fader *fader, int icon_id, Browser *browser)
:
_fader(fader), _browser(browser), _icon_id(icon_id)
{ }
/**
* Event handler interface
*/
void handle_event(Event const &ev) override
{
static int key_cnt;
if (ev.type == Event::PRESS) key_cnt++;
if (ev.type == Event::RELEASE) key_cnt--;
/* start movement with zero speed */
if ((ev.type != Event::PRESS) || (key_cnt != 1)) return;
/* no flashing by default */
int flash = 0;
switch (_icon_id) {
case ICON_HOME:
flash |= _browser->go_home();
break;
case ICON_BACKWARD:
flash |= _browser->go_backward();
break;
case ICON_FORWARD:
flash |= _browser->go_forward();
break;
case ICON_INDEX:
flash |= _browser->go_toc();
break;
case ICON_ABOUT:
flash |= _browser->go_about();
break;
}
/* flash clicked icon */
if (0 && flash) {
/* flash fader to the max */
_fader->step(4);
_fader->curr(190);
}
}
};
template <typename PT>
class Browser_sizer_event_handler : public Scout::Sizer_event_handler
{
private:
/*
* Noncopyable
*/
Browser_sizer_event_handler(Browser_sizer_event_handler const &);
Browser_sizer_event_handler &operator = (Browser_sizer_event_handler const &);
Browser_window<PT> *_browser_win;
Anchor *_ca = nullptr; /* original visible element */
/**
* Event handler interface
*/
void start_drag() override
{
Sizer_event_handler::start_drag();
_ca = _browser_win->curr_anchor();
}
void do_drag() override
{
Sizer_event_handler::do_drag();
_browser_win->go_to(_ca, 0);
}
public:
/**
* Constructor
*/
Browser_sizer_event_handler(Browser_window<PT> *browser_win)
:
Sizer_event_handler(browser_win), _browser_win(browser_win)
{ }
};
/******************************
** Browser window interface **
******************************/
template <typename PT>
Browser_window<PT>::Browser_window(Document *initial_content,
Graphics_backend &gfx_backend,
Point position, Area size,
Area max_size, Config const &config)
:
Browser(_IH + _TH), Window(gfx_backend, position, size, max_size, true),
_config(config),
_gfx_backend(gfx_backend)
{
/* init attributes */
_ypos = 0;
_document = initial_content;
/* init docview and history with initial document */
_docview.texture(&_texture);
_docview.voffset(doc_offset());
_history.add(initial_content);
/* init icons */
memset(_icon_fg, 0, sizeof(_icon_fg));
memset(_icon_fg_alpha, 0, sizeof(_icon_fg_alpha));
for (int i = 0; i < _NUM_ICONS; i++) {
/* convert rgba raw image to PT pixel format and alpha channel */
extract_rgba(COVER_RGBA, _IW, _IH,
_icon_fg[i][0], _icon_fg_alpha[i][0]);
/* assign back buffer, foreground and distmap to icon */
_icon[i].backbuf(_icon_backbuf[0], 1);
_icon[i].distmap(IOR_MAP, _IW*2, _IH*2);
_icon[i].foreground(_icon_fg[i][0], _icon_fg_alpha[i][0]);
_icon[i].event_handler(new Mover_event_handler(this));
/* apply foreground graphics to icon */
extract_rgba(glow_icon_gfx[i], _IW, _IH,
_icon_fg[i][0], _icon_fg_alpha[i][0]);
/* init glow icon */
Fade_icon<PT, _IW, _IH> *fadeicon = &_glow_icon[i];
fadeicon->glow(glow_icon_gfx[i], glow_icon_col[i]);
fadeicon->default_alpha(0);
fadeicon->focus_alpha(100);
fadeicon->alpha(0);
fadeicon->event_handler(new Iconbar_event_handler(fadeicon, i, this));
}
/*
* All icons share the same distmap. Therefore we need to scratch
* only one distmap to affect all icons.
*/
_icon[0].scratch(_SCRATCH);
/* create panel tile texture */
/*
* NOTE: The panel height must be the same as the icon height.
*/
using Scout::random;
for (int j = 0; j < _PANEL_H; j++)
for (int i = 0; i < _PANEL_W; i++) {
_panel_fg [j][i] = _icon_fg [ICON_INDEX][j][i&0x1];
_panel_fg_alpha [j][i] = _icon_fg_alpha [ICON_INDEX][j][i&0x1] + random()%3;
}
/* init panel background */
_panel.backbuf(&_panel_backbuf[0][0]);
_panel.distmap(_panel_distmap[0], _PANEL_W*2, _PANEL_H*2);
_panel.foreground(_panel_fg[0], _panel_fg_alpha[0]);
_panel.scratch(_SCRATCH);
_panel.event_handler(new Mover_event_handler(this));
/* resize handle */
_sizer.rgba(SIZER_RGBA);
_sizer.event_handler(new Browser_sizer_event_handler<PT>(this));
_sizer.alpha(100);
/* titlebar */
_titlebar.rgba(TITLEBAR_RGBA);
_titlebar.text(_document->title);
_titlebar.event_handler(new Mover_event_handler(this));
_min_size = Scout::Area(_NUM_ICONS*_IW, _IH + 250);
/* adopt widgets as child elements */
append(&_docview);
for (int i = 0; i <= ICON_INDEX; i++) {
append(&_icon[i]);
append(&_glow_icon[i]);
}
append(&_panel);
append(&_icon[ICON_ABOUT]);
append(&_glow_icon[ICON_ABOUT]);
append(&_shadow);
append(&_scrollbar);
if (_attr & ATTR_SIZER) append(&_sizer);
if (_attr & ATTR_TITLEBAR) append(&_titlebar);
_scrollbar.listener(this);
_content(initial_content);
}
template <typename PT>
void Browser_window<PT>::ypos_sb(int ypos, int update_scrollbar)
{
if (ypos < -(int)_docview.size().h() + (int)_size.h())
ypos = -(int)_docview.size().h() + (int)_size.h();
_ypos = ypos <= 0 ? ypos : 0;
_docview.geometry(Rect(Point(_docview.position().x(), _ypos),
Area(_docview.size().w(), _docview.size().h())));
if (update_scrollbar)
_scrollbar.view(_docview.size().h(), _size.h(), -_ypos);
refresh();
}
/***********************
** Browser interface **
***********************/
template <typename PT>
Element *Browser_window<PT>::_content()
{
return _docview.content();
}
template <typename PT>
void Browser_window<PT>::_content(Element *content)
{
if (!content || (content == _docview.content())) return;
content->fill_cache(_gfx_backend.front());
_docview.content(content);
format(_size);
_ypos = 0;
}
template <typename PT>
void Browser_window<PT>::format(Area size)
{
unsigned w = size.w();
unsigned h = size.h();
/* limit browser window size to valid values */
w = max(w, min_size().w());
h = max(h, min_size().h());
w = min(w, max_size().w());
h = min(h, max_size().h());
/* determine old scrollbar visibility */
int old_sb_visibility = (_docview.min_size().h() > _size.h());
/* assign new size to browser window */
_size = Scout::Area(w, h);
/* format document */
_docview.format_fixed_width(_size.w());
/* format titlebar */
_titlebar.format_fixed_width(_size.w());
/* determine new scrollbar visibility */
int new_sb_visibility = (_docview.min_size().h() > _size.h());
/* reformat docview on change of scrollbar visibility */
if (old_sb_visibility ^ new_sb_visibility) {
_docview.right_pad(new_sb_visibility ? _scrollbar.min_size().w() : 0);
_docview.format_fixed_width(_size.w());
}
/* position docview */
_docview.geometry(Rect(Point(0, _ypos),
Area(_docview.min_size().w(),
max(_docview.min_size().h(), _size.h()))));
/* start at top */
int y = 0;
/* position titlebar */
if (_attr & ATTR_TITLEBAR) {
_titlebar.geometry(Rect(Point(y, 0), Area(_size.w(), _TH)));
y += _TH;
}
/* position icons */
for (int i = 0; i <= ICON_INDEX; i++) {
_icon[i].geometry(Rect(Point(i*_IW, y), Area(_IW, _IH)));
_glow_icon[i].geometry(Rect(Point(i*_IW, y), Area(_IW, _IH)));
}
_icon[ICON_ABOUT].geometry(Rect(Point(_size.w() - _IW, y), Area(_IW, _IH)));
_glow_icon[ICON_ABOUT].geometry(Rect(Point(_size.w() - _IW, y), Area(_IW, _IH)));
/* the panel is the space between the left icon set and the right about icon */
int panel_x = _icon[ICON_INDEX].position().x() + _IW;
_panel.geometry(Rect(Point(panel_x, y),
Area(_icon[ICON_ABOUT].position().x() - panel_x, _IH)));
y += _IH;
_scrollbar.geometry(Rect(Point(w - _scrollbar.min_size().w() - _SB_XPAD, y + _SB_YPAD),
Area(_scrollbar.min_size().w(),
h - y - _SB_YPAD*2 - (_attr & ATTR_SIZER ? 8 : 0))));
_shadow.geometry(Rect(Point(0, y), Area(_size.w(), 10)));
if (_attr & ATTR_SIZER)
_sizer.geometry(Rect(Point(_size.w() - 32, _size.h() - 32), Area(32, 32)));
Window::format(_size);
}
template <typename PT>
Anchor *Browser_window<PT>::curr_anchor() { return find_by_y(doc_offset()); }
/**********************************
** Scrollbar listener interface **
**********************************/
template <typename PT>
void Browser_window<PT>::handle_scroll(int view_pos)
{
/*
* The handle scroll notification comes from the scrollbar,
* which already adjusted itself to the new view port.
* Therefore, we do not need to re-adjust it another time
* and call ypos() with update_scrollbar set to zero.
*/
ypos_sb(-view_pos, 0);
}
template class Browser_window<Genode::Pixel_rgb565>;