/*
 * Copyright 2018-2024, ttcoder of TTS LLC
 * Distributed under the terms of the MIT license.
 */


// genode/base.lib.a
#include <os/pixel_alpha8.h>///
#include <os/texture_rgb888.h>
#include <base/heap.h>

// genode/base.lib.a extras..
//#include <base/debug.h>  // PDBG()
#if 1
#undef PDBG
#define PDBG  Genode::log
#endif
#include <gems/chunky_texture.h>
#include <nitpicker_gfx/box_painter.h>
#include <nitpicker_gfx/texture_painter.h>
#include <nitpicker_gfx/tff_font.h>
#include <polygon_gfx/line_painter.h>
#include <polygon_gfx/shaded_polygon_painter.h>

// libc.lib.so
#include <stdlib.h>  // malloc()

// haiku.lib.so
#include "_GeFontStash.h"
#include <app/Application.h>  // be_app
#include <interface/AffineTransform.h>
#include <interface/Bitmap.h>
#include <interface/Gradient.h>
#include <interface/Polygon.h>
#include "ColorConversion.h"  // BPrivate::ConvertBits()

// this
#include "_CanvasGenode.h"



/**************/
static int debug = 0;
static int debug_caps = 0;


static
BString stringFor( rgb_color c )
{
	// RETURNS
	// e.g. " #FFF" for color "white 255"..
	
	BString s;
	s.SetToFormat( " #%X%X%X:%x", c.red/16, c.green/16, c.blue/16, c.alpha/16 );
	
	return s;
}

static
BString stringFor( const BRect & r )
{
	BString s;
	{
		s
			<< " ltrb: "
			<< int(r.left) << ", " << int(r.top) << ", "
			<< int(r.right) << ", " << int(r.bottom)
			;
	}
	return s;
}



//#pragma mark - class CanvasGenode -


CanvasGenode::CanvasGenode(
			Genode::Env & genode_env,
			Gui::Connection & server_events,
			BBitmap * use_offscreen_painting
			)
:	env( genode_env )
	,nitServer( server_events )
	,screenCanvas( nullptr )
	,memoryCanvas( use_offscreen_painting )
#ifdef HAS_MICRO_WINDOW_MANAGER
	,csdTitle()
	,csdTab()
	,csdBorder( 0. )
#endif
	,_size( 100,100)///(1, 1)
#if 0
	,_glyph_buffer()
	//,_font_1()
#endif
	,currentOrigin()
	,clipRect()
{
	if( debug )
		PDBG( "" );  // just __pretty_func__
	
	// Upstream/reference code:
	// os/src/test/nitpicker
	
	if( memoryCanvas )
	{
		const BRect r = memoryCanvas->Bounds();
		
		// T-
		_size = Genode::Surface_base::Area( r.Width()+1, r.Height()+1 );
		
		// T+
		SetOriginAndClipping( B_ORIGIN, r );
		
		//
		return;
		//
	}
	
	const Framebuffer::Mode mode
	{
		.area = { _size.w, _size.h },
		.alpha = false  // use new 'alpha' field as per ticket #5353
	};
	
	// setup virtual framebuffer mode
	nitServer.buffer( mode );
	
	// it should return us the requested px:
	int const
		scr_w = _size.w,
		scr_h = _size.h;
	
	if( debug >= 1 )
		Genode::log( " screen is ", mode );
	
	if( !scr_w || !scr_h )
	{
		Genode::error("CanvasGenode: got invalid screen - aborting");
		//
		return;
		//
	}
	
	// alloc RM
	
	if( debug_caps )
		Genode::log( "\nt- Attached_dataspace_fbuffer ctor" );
	
	screenCanvas = new Genode::Attached_dataspace (
		env.rm(),
		server_events.framebuffer.dataspace()
		);
	
	if( debug_caps )
		Genode::log( "t+ Attached_dataspace_fbuffer ctor" );
	
	// clear framebuffer..
	//memset(_fb_ds->local_addr<pixel_t>(), 0, _fb_ds->size());
	
	SetOriginAndClipping( BPoint(), BRect() );
	
#if 0
	/******* Visual debugging : checkers pattern to dis-ambiguate window geometry *******/
	
	// setviewport( no clipping at all ) first thing, or the below won't work :
	SetOriginAndClipping( BPoint(0,0), BRect(0,0,999,999) );
	
	// display a "bare framebuffer" checkers pattern, to differentiate "no drawing done yet" and "all-black drawing" cases
	BRect r( 0,0,31,31 );  // 32-px-size squares
	for( int i = 0; i < 20; i++ )
	{
		int c = 200 -i*10;
		FillRect( r, make_color(c, c, c) );
		r.OffsetBy( 32., 32. );
	}
	
	Update();
#endif
}


CanvasGenode::~CanvasGenode()
{
	if( debug_caps )
		Genode::log( "t- DTOR Attached_dataspace_fbuffer" );
	
	delete screenCanvas;  screenCanvas = NULL;
	
	if( debug_caps )
		Genode::log( "t+ DTOR Attached_dataspace_fbuffer" );
}



void CanvasGenode::Update()
{
	if( memoryCanvas )
		//
		return;  // nothing to sync, off-screen bitmaps are single-buffered
		//
	
	if( debug >= 3 )
		Genode::log( "    => blit to NitPicker <= ----------" );
	
	nitServer.framebuffer.refresh(
		0, 0,
		_size.w, _size.h //+clientFrame instead
		);
}

///later: talking of drawing bugs and 'missing lines': I should roundf() maybe ?
// see:  https://xref.landonf.org/source/xref/haiku/src/servers/app/drawing/drawing_support.cpp#9
// align_rect_to_pixels(BRect* rect) -> rect->right = roundf(rect->right); and so on


//#pragma mark - > Client-Side Decorations (if any) -


#ifdef HAS_MICRO_WINDOW_MANAGER

void CanvasGenode::SetInsets_DecorCS( BRect tab_frame, float border_thickness )
{
	if( debug )
		PDBG( "setdecor border-width: ", border_thickness, " tab-height: ", tab_frame.Height() +1., " for: ", csdTitle.String() );
	
	csdTab = tab_frame;
	csdBorder = border_thickness;
	
	updateDecor();
}

void CanvasGenode::SetTitle_DecorCS( const char * title )
{
	csdTitle = title;
	
	updateDecor();
}

Gui::Rect CanvasGenode::DecoratedOuterRect_For_Inner( const Rect inner_rect ) const
{
//open /GenCharlie/_nimble_Master/dev_github.com/genode/repos/os/include/util/geometry.h
	// enlarge the inner frame by "csdBorder" pixels, to obtain the outer (decorated) frame
	Rect outer_rect = Rect::compound(
		Point(
			inner_rect.x1() - csdBorder,
			inner_rect.y1() - csdBorder - (csdTab.Height() +1.) ), //xx support left-hand tab..
		Point(
			inner_rect.x2() + csdBorder,
			inner_rect.y2() + csdBorder )
		);
	
	return outer_rect;
}

void CanvasGenode::updateDecor()
{
	if( debug )
		PDBG( "updateDecor: csdBorder=", csdBorder );
	
	if( csdBorder <= 0. && false==csdTab.IsValid() )
		//
		return;
		//
	
	// T- prologue
	const BPoint save_orig = currentOrigin;
	const BRect  save_clip = clipRect;
	
	// With the above which saves clipping & origin (some apps do misbehave if we don't, like
	// Clock), we can now override them, so as to paint decorations, outside "client" area:
	currentOrigin = BPoint( 0., 0. );
	clipRect = BRect( currentOrigin, BPoint(_size.w -1, _size.h -1) );
	
///ToDo-2: upgrade from "bare bones" decorator to actual Haiku-code decorator
	// -- bare-bones decorator --
	
	//xx implement support for left-side tab
	const float tab_hei = csdTab.IsValid()
		? csdTab.Height() +1.
		: 0.
		;
	
	// T-
	// Be-like border
	{
		// # note #: this is hardcoded for 5px thick borders, does not support assymetric left/right edges, etc..
		BRect r( clipRect );
		r.top = tab_hei; ///later: -1, to overlap one line (out of five) like Haiku? keep the rest of tab_frame.Height() dependant code in sync though ; or maybe it would be a matter of doing tab_frame.OffsetBy(0, 1) in BWindow ; in fact I should distinguish "tab" as in "the rect that protrudes above the main window rect" vs the "yellow fill", which extends one pixel lower than the "tab" rect
		StrokeRect( r, make_color(157, 157, 157) ); r.InsetBy( 1., 1. );
		StrokeRect( r, make_color(243, 243, 243) ); r.InsetBy( 1., 1. );
		StrokeRect( r, make_color(224, 224, 224) ); r.InsetBy( 1., 1. );
		StrokeRect( r, make_color(207, 207, 207) ); r.InsetBy( 1., 1. );
		StrokeRect( r, make_color(157, 157, 157) );
	}
	
	//xx or just use csdTab !
	BRect tab( clipRect );
	tab.bottom = tab_hei -1.;
	const rgb_color gray = make_color( 70, 70, 20 );
	
	// T+
	// (not-so-)yellow tab
	if( tab_hei > 5. ) //xx should be "> 0" ?
	{
		BGradient gra;
		gra.AddColor( make_color(150, 150, 150), 0. );
		gra.AddColor( make_color(200, 170,   0), 0. );
		FillRect( tab, gra );
	}
	
	// tab's "close" button
	if( tab_hei > 5. )
	{
		BRect r( tab );
		r.InsetBy( 5, 5 );
		r.right = r.bottom;  // square
		
		StrokeRect( r, gray );
	}
	
	// tab's title
	if( tab_hei > 5. )
	{
		BPoint where;
		{
			BRect r( tab );
			r.left = tab_hei + 3;
			
			// vert centering:
			if( be_app )
			{
				const int font_id = 0;
				const Text_painter::Font & font =
					*be_app->GenoFonts().Font( font_id ).tpainterFont
					;
				r.bottom /= 2;
				r.bottom += font.baseline() / 2;
			}
			
			where = r.LeftBottom();
		}
		
		DrawString(
			csdTitle, csdTitle.Length(),
			where,
			gray
			);
	}
	
	// T+ epilogue
	Update();
	currentOrigin = save_orig;
	clipRect = save_clip;
}

#endif  // ~HAS_MICRO_WINDOW_MANAGER


void CanvasGenode::Rebuffer_Inner( const Gui::Rect inner_rect )
{
	// AKA "resize_buffer_for_this_inner_frame"
	
#ifdef HAS_MICRO_WINDOW_MANAGER
	Rect outer_rect =
		DecoratedOuterRect_For_Inner( inner_rect );
	
	rebufferOuter( outer_rect.area );

#else  // no-client-side-deco

	Rect outer_rect = inner_rect;
	rebufferOuter( outer_rect.area );
#endif  // ~no-client-side-deco
}



//#pragma mark - > decoration-agnostic (almost) remainder of the code


void CanvasGenode::rebufferOuter( const Gui::Area outer_area )
{
	using namespace Gui;
	
	if( memoryCanvas )
		//
		return;  //+ or maybe implement this, and call this from ctor, but with ZEROed decorator area
		//
	
	if( debug >= 1 )
		PDBG( "resize: ", outer_area, " (outer area)" );//, " for window <", csdTitle.String(), ">" );
	
	/********/
	
	//?
	SetOriginAndClipping( BPoint(), BRect() );//	SetOriginAndClipping( B_ORIGIN, clientFrame );
	
	int width  = outer_area.w;
	int height = outer_area.h;
	
	// T0
	_size = Genode::Surface_base::Area( width, height );
	//PDBG( "  final size: ", _size );
	
	/********/
	
	// ++ Dodge 0-width/height resizing ++
	/*
	Otherwise the app gets killed by an "Invalid_dataspace" exception:
		[init -> Mandelbrot] CanvasGenode::rebuffer(0 0)
		[init -> Mandelbrot] Error: Thread 'menu_tracking' died because of an uncaught exception
		[init -> Mandelbrot] Error: Uncaught exception of type 'Genode::Region_map::Invalid_dataspace'
		[init -> Mandelbrot] Warning: abort called - thread: menu_tracking
		[init] child "Mandelbrot" exited with exit value 1
	*/
	if( width < 1 || height < 1 )
		//
		return;
		//
	
	//if (max_x > _size.width() || max_y > _size.height())
	{
		const Framebuffer::Mode mode
		{
			.area = { _size.w, _size.h },
			.alpha = false
		};
		nitServer.buffer( mode );
		
		if( debug_caps )
			Genode::log( "t- Attached_dataspace_fbuffer ReCycle.." );
		
		if( screenCanvas )
			delete screenCanvas;
		screenCanvas = nullptr;
		;
		screenCanvas = new Genode::Attached_dataspace(
			env.rm(),
			nitServer.framebuffer.dataspace()
			);
		
		if( debug_caps )
			Genode::log( "t+ Attached_dataspace_fbuffer ReCycle.." );
	}
	
	// T+
#ifdef HAS_MICRO_WINDOW_MANAGER
	updateDecor();
#endif
	
	if( debug >= 9 )
		Genode::log( "~CanvasGenode::Rebuffer() done" );
}



void CanvasGenode::SetOriginAndClipping( BPoint origin, BRect clipping )
{
	// CONTEXT
	// As each BView's Draw() hook is called for drawing
	// in its own coordinates referential, this method
	// is called at T-. It sets up the offset (relative origin)
	// between said coordinates and the BWindow/framebuffer
	// coordinates, so that each view is drawn at the right location.
	// Decorators:
	// This is not suitable for drawing outside the inner area as we
	// unconditionally constrain <currentOrigin> within <csdBorder> pixels,
	// so decorator code has to set <currentOrigin> by hand instead.
	
	if( clipping.left < origin.x || clipping.top < origin.y )
		Genode::error( "canvas.setorigin: something is weird with the passed origin/clipping (", origin.x, " : ", origin.y, " vs. ", clipping.left, " : ", clipping.top, ")" );//, origin, " vs ", clipping );
	
	if( false == clipping.IsValid() )
	{
		//+ the 'update' mechanism is triggered by( or at?) Addchild(), so WITHOUT a SetViewPort !
		//so do 9999 to disable drawing out-of-band
		//
		//xx this 9999 stuff will crash if not clipped, and leave the window black if clipped !
		//+ maybe a simple "bool will-draw", set to false if 1) out-of-band or 2) IsHidden() or 3) no B-will-draw ?
		//+ also should support "out of band" drawing anyway some day..
		//=> eventually implemented upstream, Window.UpdateIfNeeded() calls "if !IsHidden then Draw()"..
		
		clipRect = BRect();
		
		//
		return;
	}
	
	// adjust client-reference to framebuffer (decorator-aware) reference frame
	//
	BRect client_frame;  // aka client-Side-Inner-Frame
#ifdef HAS_MICRO_WINDOW_MANAGER
	client_frame = BRect(
		0			+ csdBorder,
		0			+ csdBorder + csdTab.Height()+1.0,
		_size.w -1 -csdBorder,
		_size.h -1 -csdBorder
		);

#else  // no_CS_WINMAN:

	client_frame = BRect(
		0., 0.,
		_size.w() -1,
		_size.h() -1
		);
#endif  // ~no_CS_WINMAN
	
	origin += client_frame.LeftTop();
	clipping.OffsetBy( client_frame.LeftTop() );
	//
	// upcoming painting coords will be relative to this offset
	currentOrigin = origin;
	
	// T-
	if( debug >= 3 )
		Genode::log( "  SetOriginAndClipping ", int(origin.x), ", ", int(origin.y), " ", stringFor(clipping).String() );
	
	// chop off the excess -- when the BView's BWindow has e.g. been resized
	// too small to display its right-most or bottom-most view(s).
	// some of our drawing primitives need the 'protection', some do not.. need to cater to the lowest denominator here..
	if( clipping.right > client_frame.right )
	{
//		Genode::warning( "attempting to set viewport beyond window/FB: ", offset_and_clipping.right, " ", offset_and_clipping.bottom, " vs ", _size.w(), " ", _size.h(), " ---------" );
		clipping.right = client_frame.right;
	}
	if( clipping.bottom > client_frame.bottom )
	{
//		Genode::warning( "attempting to set viewport beyond window/FB: ", offset_and_clipping.right, " ", offset_and_clipping.bottom, " vs ", _size.w(), " ", _size.h(), " ---------" );
		clipping.bottom = client_frame.bottom;
	}
	
	if( clipping.left < 0 || clipping.top < 0 || clipping.right < 0 || clipping.bottom < 0 )
	{
		Genode::error( "invalid viewport ! illegal negative coords: ", stringFor(clipping).String() );
		clipRect = BRect();
		
		//
		return;
		//
	}
	
	// T0
	clipRect = clipping;
}



#if 0
///later: transparency: investigate the below for implementing arbitrary shape windows, e.g. Pie Menu
// (i.e. transparency and alpha-blending Genode code, CONFIG_ALPHA etc)

void CanvasGenode::FillRainbow()
{
	int scr_w = _size.w();
	int scr_h = _size.h();
	
	//Genode::log(" canvas: paint rainbow");
	enum { CONFIG_ALPHA = false };
	short *pixels = fb_ds->local_addr<short>();
	unsigned char *alpha = (unsigned char *)&pixels[scr_w*scr_h];
	unsigned char *input_mask = CONFIG_ALPHA ? alpha + scr_w*scr_h : 0;

	/*
	 * Paint into pixel buffer, fill alpha channel and input-mask buffer
	 *
	 * Input should refer to the view if the alpha value is more than 50%.
	 */
	for (int i = 0; i < scr_h; i++)
	{
		for (int j = 0; j < scr_w; j++)
		{
			pixels[i*scr_w + j] =
				(i/8)*32*64 + (j/4)*32 + i*j/256
				;
			
			if (CONFIG_ALPHA)
			{
				alpha[i*scr_w + j] = (i*2) ^ (j*2);
				input_mask[i*scr_w + j] = alpha[i*scr_w + j] > 127;
			}
		}
	}
}
#endif



void CanvasGenode::setPixel_LocalCoords( int x_local, int y_local, rgb_color color )
{
	//if( x_local < 0 || y_local < 0 )
	//	debugger( "negative pixel" );
	
	//Genode::log( "     setpixel: local x-y ", x_local, " , ", y_local );
	
	// view/local-coords => window/framebuffer-coords:
	int x = x_local + currentOrigin.x;
	int y = y_local + currentOrigin.y;
	
	// apply clipping (to avoid visual bugs, and to avoid crashing if hitting outside of framebuffer!)
	if( x > clipRect.right || y > clipRect.bottom
		|| x < clipRect.left || y < clipRect.top
		)
	{
		// just ignore, this is a common occurence (if e.g. a BWindow is resized
		// down such that some BViews are out of bounds)
		//_d_		Genode::warning("clip! ", x, " ", y, " ->bigger than ", clipRect.right, " ", clipRect.bottom);
		
		//
		return;
		//
	}
	
	if( memoryCanvas )
	{
		switch( memoryCanvas->ColorSpace() )
		{
			case B_CMAP8:
			{
				uint8 * pixels = (uint8*)memoryCanvas->Bits();
///later (if significantly used beyond Clock needles): implement CMAP8 offscreen painting, instead of red-only-hue quick hack:
				pixels[ x + y*memoryCanvas->BytesPerRow() ] = color.red;//65;//50;//////
			}
			break;
			
			//case B_RGB24:
			//case B_RGB32:
			default:
			{
				Genode::warning( "unimplemented color space in setpixel()" );
			}
			break;
		}
	}
	else  // screenCanvas
	{
		PT * pixels = screenCanvas->local_addr<PT>();
		
		const Genode::Color
			genodecolor( color.red, color.green, color.blue, color.alpha )
			;
		PT pixel;
		pixel.rgba( color.red, color.green, color.blue );
		
		if( x + y*_size.w >= screenCanvas->size() )
		{
			// this ought to have been caught by the <clippingPort> test above...:
			Genode::error("framebuffer: attempting to write beyond FB addy space! FB size: ", screenCanvas->size(), " wanted offset: ", x + y*_size.w, " ---------");
			
			//
			return;
			//
		}
		
		PT & dst = pixels[ x + y*_size.w ];
		
		// T0
		//
		if( genodecolor.opaque() )
			dst = pixel;
		else
			// alpha blending
			dst = PT::mix( dst, pixel, genodecolor.a );
	}
}



//#pragma mark - line/polygon/rect -


void CanvasGenode::StrokeLine( BPoint p1, BPoint p2, rgb_color color )
{
	if( debug >= 5 )
		Genode::log( "   strokeline: ", p1.x, " , ", p1.y, " / ", p2.x, " , ", p2.y, stringFor(color).String() );
	
	///later: +++Optim: keep Line_painter as a member
	//Line_painter::paint(
	Line_painter lp;
	
	// convert view coords to window coordinates
	p1 += currentOrigin;
	p2 += currentOrigin;
	
#if 0
	// debug: draw a CMAP8 rainbow...
	//if( memoryCanvas )
	{
		for(int y=0+5;y< /*82*/20-5;y++)
		{
			for( int x=0+5; x<82-5;x++)
			{
				setPixel_LocalCoords(x,y,make_color(y,0,0));  // hack a CMAP8 index though an rgb_color's red component (with collaborative hack in setpixel()..)
			}
		}
		// draw a (theoritically perfectly diagonal) line... if aspect ratio is wrong this will be quickly apparent..
		for( int i=0; i<82;i++)
		{
			setPixel_LocalCoords(i, i, make_color(6,0,0));
		}
	}
#endif
	
	Rect clip(
		Point( clipRect.left, clipRect.top ),
		Area( clipRect.Width() +1, clipRect.Height() +1 )
		);
	Genode::Color gcolor(
		color.red, color.green, color.blue
		);
///ToDo-2: debug StrokeLine missing lines in e.g. BTextControl, using eg the following "red shade" debugging aid:
//+		color.red, 0, 0

	
///ToDo-2: seems Line_painter cannot draw a half-clipped line, it rejects it as a whole...
// Use our fallback code instead, in both "mem canvas" and "screen canvas" cases:
#if 0
	if( memoryCanvas )
#else
	if( true )
#endif
	{
		////later: switch( memoryCanvas->ColorSpace.. )
#if 0
		Genode::Pixel_alpha8 * pixels = (Genode::Pixel_alpha8*)
			memoryCanvas->Bits();
		Genode::Surface< Genode::Pixel_alpha8 >
			surface {
				pixels,
				Area(
					memoryCanvas->BytesPerRow(), //+ this works "by chance" in CMAP8, clean up with eg: get_raw_bytes_per_row(memoryCanvas->Bounds().Width()) / pixelsize...,
					memoryCanvas->Bounds().Height() +1)
				};
		
		surface.clip( clip );
		lp.paint(
			surface,
			Point( p1.x, p1.y ),
			Point( p2.x, p2.y ),
			gcolor
			);
#else
		// Missing support for B_CMAP8 pixels (Pixel_alpha8 is close but does not quite match), so let's roll our own code for now:
		
		// restore back to local coords, since we use setPixel_LocalCoords()
		p1 -= currentOrigin;
		p2 -= currentOrigin;
		
		// -- Simple line drawing, using pixel "steps" --
		// ..careful not to forget to also paint the last pixel..
		
		BPoint beg;
		BPoint end;
		float dx = 0.;
		float dy = 0.;
		float steps = 0.;  // aka "number of pixels to draw" (since we don't do AA we draw just one pixel per step)
			// well actually, "number of pixels Minus One" (see the for() loop below)
		{
			beg = p1;
			end = p2;
			
			// if e.g. beg==end, these may collapse to zero (since they are pixel-count-minus-one, see below)
			float abs_dx = abs( end.x - beg.x );
			float abs_dy = abs( end.y - beg.y );
			
			// sort: we need beginning-end ordering, so that
			// the "i < steps" test ..etc will work as intended
			if( abs_dx > abs_dy )
				steps = abs_dx;
			else
				steps = abs_dy;
			
			// same remark as above
			dx = end.x - beg.x;
			dy = end.y - beg.y;
		}
		
		//Genode::log( "   dx: ", dx, " beg.x: ", beg.x, " (Steps: ", steps, ")" );
		//Genode::log( "   dy: ", dy, " beg.y: ", beg.y );
		
		// Careful about the "missing pixel" problem
		// (see unit test in basic-layers.cpp that draws with
		//  strokeline: 8.0 , 8.0 / 13.0 , 8.0 and so on) :
		//
		// We want to iterate from i==0 to paint the first pixel exactly on 'beg',
		// up to i==steps so as to pain the last pixel exactly on 'end'.
		// Which means we iterate from 0 to 'steps' *inclusive*, since 'steps' is pixel-count-minus-one
		//
		for( float i = 0.; i <= steps; i += 1. )
		{
			// dodge divide-by-zero, since there might be only one pixel to paint, in which case steps == 1-1 == 0
			float local_x = beg.x;
			float local_y = beg.y;
			if( steps > 0 )
			{
				local_x += (i * dx)/steps;
				local_y += (i * dy)/steps;
			}
			//Genode::log( "    x/y: ", local_x, " ", local_y);
			//Genode::log( "     (dx: ", dx, " i: ", i, " steps: ", steps, ")" );
			
			// clippingPort.left ..etc are added in setPixel() itself:
			setPixel_LocalCoords( local_x, local_y, color );
		}
#endif
	}
	else  // screenCanvas
	{
		Genode::Surface< PT >
			surface { screenCanvas->local_addr<PT>(), _size };
		
		surface.clip( clip );
		
		// Missing-pixel bug: oddly enough, Genode has the same bug that I used
		// to have, i.e. it paints pixels from p1 (inclusive) to p2 (NON-inclusive).
		// We work-around that by painting twice, beginning-to-end, and then end-to-beginning.
		lp.paint(
			surface,
			Point( p1.x, p1.y ),
			Point( p2.x, p2.y ),
			gcolor
			);
		lp.paint(
			surface,
			Point( p2.x, p2.y ),
			Point( p1.x, p1.y ),
			gcolor
			);
	}
}


void CanvasGenode::StrokeTriangle( BPoint p1, BPoint p2, BPoint p3, rgb_color color )
{
	if( debug >= 4 )
		Genode::log( "   stroketriangle, p1: ", p1.x, " , ", p1.y, stringFor(color).String() );
	
	// NOTE: we're just an alias for StrokeLine(), delegate handling of clipping and mem-canvas to it.
	
	StrokeLine( p1, p2, color );
	StrokeLine( p2, p3, color );
	StrokeLine( p3, p1, color );
}


void CanvasGenode::FillTriangle( BPoint p1, BPoint p2, BPoint p3, rgb_color color  )
{
	if( debug >= 4 )
		Genode::log( "   filltriangle, p1: ", p1.x, " , ", p1.y, stringFor(color).String() );
	
///later: implement BBitmap painting / FillTriangle
	if( memoryCanvas )
		//
		return;
		//
	
	Genode::Surface<PT>
		surface { screenCanvas->local_addr<PT>(), _size };
	{
		surface.clip( Rect(
			Point( clipRect.left, clipRect.top ),
			Area( clipRect.Width() +1, clipRect.Height() +1 )
			) );
	}
	
	Polygon::Shaded_painter::Point
		points[3];
	{
		const Genode::Color col = Genode::Color( color.red, color.green, color.blue );
		p1 += currentOrigin;
		p2 += currentOrigin;
		p3 += currentOrigin;
		
		points[0] = Polygon::Shaded_painter::Point( p1.x, p1.y, col );
		points[1] = Polygon::Shaded_painter::Point( p2.x, p2.y, col );
		points[2] = Polygon::Shaded_painter::Point( p3.x, p3.y, col );
	}
	
#if 0
	Genode::Heap heap { env.ram(), env.rm() }; ///later: optim
	Polygon::Shaded_painter painter(
		heap,
		clipRect.Height() //? or maybe height of the BView ?
		);
	painter.paint(
		surface,
		surface, //xxx same surface for "alpha"?
		points,
		3
		);
#else
///ToDo-2: FillTriangle() disabled (redirected to StrokePolygon) for now as it "crashes" ; look into why Polygon::Shaded_painter makes a "zero alloc" sometimes
///(alternatively, I could come up with my own FillTriangle algo -- similar to FillArc's use of "line equation factor", but using 3 line/equations instead of 2 lines + a circular edge
	BPoint a[] = { p1, p2, p3 };
	BPolygon polygon( a, B_COUNT_OF(a) );
	StrokePolygon( polygon, a, color );
#endif
	
	if( debug >= 4 )
		Genode::log( "   leaving filltriangle......" );
}


void CanvasGenode::StrokePolygon( const BPolygon & polygon, const BPoint priv_points[], rgb_color color )
{
	if( debug >= 4 )
		Genode::log( "   strokepolygon, ", polygon.CountPoints(), " points, ", stringFor(color).String() );
	
	// NOTE: we're just an alias for StrokeLine(), delegate handling of clipping and mem-canvas to it.
	
	for( int i = 0; i < polygon.CountPoints(); i++ )
	{
		BPoint next;
		if( i+1 < polygon.CountPoints() )
			next = priv_points[i+1];
		else
			next = priv_points[0];  // circle last-point back to first-point
		
		StrokeLine( priv_points[i], next, color );
	}
}


void  CanvasGenode::FillPolygon( const BPolygon & polygon, const BPoint priv_points[], rgb_color col )
{
	if( debug >= 4 )
		Genode::warning( "   fillpolygon, ", polygon.CountPoints(), " points, ");//, stringFor(color).String() );
	
	//+ need to test this (in AK ?)
	BGradient pseudo_gradient;
	pseudo_gradient.AddColor( col, 0. );
//	pseudo_gradient.AddColorStop( col );
	FillPolygon( polygon, priv_points, pseudo_gradient );
	
	///later: FillPolygon() used to raise an error/exception, back when it was used
	// as a ersatz for FillArc(). The trigger was e.g. in MMD:
	//	FillPolygon( polygon, p, color );
	//		<< Error: attempt to allocate zero-size block from heap >>  << Thread 'pthread.10' died because of an uncaught exception >>
	// The FillArc() code has since changed, but if one ever wants to return to debugging that, the last seen instance to reproduce the bug was:
	// b2d4d377673a52189e58a801200984515be10c0e 2024-01-01
}


void CanvasGenode::FillPolygon( const BPolygon & polygon, const BPoint priv_points[], const BGradient & gradient )
{
	if( debug >= 4 )
		Genode::log( "   fillpolygon-gradient, ", polygon.CountPoints(), " points within ", stringFor(polygon.Frame()).String() );
	
	///later ....
	if( memoryCanvas )
		//
		return;
		//
	
	Genode::Surface<PT>
		surface { screenCanvas->local_addr<PT>(), _size };
	{
		surface.clip( Rect(
			Point( clipRect.left, clipRect.top ),
			Area( clipRect.Width() +1, clipRect.Height() +1 )
			) );
	}
	
	//dont work on a copy otherwise priv_points will refer to the old points !  : make the offseting inside the for() down there instead
	/*
	// offset the points by <currentOrigin>:
	BPolygon polygon_local
	{
		polygon_local = polygon_wincoords
		
		BRect r1( 1,1,1,1 );
		BRect r2;
		{
			r2 = r1;  r2.OffsetBy( currentOrigin );
		}
		
		polygon_local.MapTo( r1, r2 );
	}
	*/
///ToDo-2: allocate on the heap (not on the stack), based on actual polygon.Count() count
	Polygon::Shaded_painter::Point
		points[32] = { };
	for( int i = 0; i < Genode::min(32, polygon.CountPoints()); i++ )
	{
		rgb_color color = make_color( 0, 0, 0 );
///later-Gradients: this is not quite like Haiku : due to Polygon:: API we only have "extremities", instead of (up to) 256 differents color values in BGradient ; works well enough for FillRect() (which delegates to us) but probably not for ArmyKnifeTTS
		Genode::Color col;
		{
			// hack: split between "top-color" and "bottom-color", for when we're called by FillRect(gradient):
			BGradient::ColorStop * step = gradient.ColorStopAt( i < 2 ? 0 : gradient.CountColorStops()-1 );
			if( step )
				color = step->color;
			col = Genode::Color( color.red, color.green, color.blue, color.alpha );
		}
		
		// offset to <currentOrigin>
		Polygon::Shaded_painter::Point p(
			priv_points[i].x + currentOrigin.x,
			priv_points[i].y + currentOrigin.y,
			col
			);
		
		points[i] = p;
		//Genode::log( "      poly-point: ", p, " color: ", col );
	}
	
	Genode::Heap heap { env.ram(), env.rm() }; ///ToDo: OPTIM! Keep the heap around (used by FillRect(BGradient) in title bars and menus)
	Polygon::Shaded_painter painter(
		heap,
		polygon.Frame().Height() +1//clipRect.Height() //?
		);
	
///ToDo: work-around for Shaded_painter: any other way to do this ?
	// Alpha needs to be redirected to a "dummy" buffer to avoid visual artifacts in the screen buffer (white rects of 1/4th size of the rect)
	// Even if I wanted to work-around that bug (?) by applying clipping to alpha, I can't
	// since interpolate_rgba() unconditionally writes alpha values to the whole surface, without applying clipping,
	// so not sure what other hack to use other than this hack:
static
	PT alphabuf[1*1024*1024] = {};  // fortunately, this does not impact the size of haiku.lib.so (BSS hunk ?)
	memset(alphabuf, '\0', sizeof(alphabuf));//
	Genode::Surface<PT>
		null_alpha { alphabuf, _size };
	{
		// not enforced :-/ :
		// null_alpha.clip( Rect() );
		// so due to that problem we gotta have a dummy null_alpha and pass that, we can't pass the genuine article ('surface')
	}
	
	//Genode::log( "  countpoints: ", polygon.CountPoints(), " size: ", _size );
	
	painter.paint(
		surface,
		null_alpha,//surface,
		points,
		polygon.CountPoints()
		);
	//surface.flush_pixels(surface.clip());// clipped );
}



void CanvasGenode::FillRect( BRect rect_local, const BGradient & gradient )
{
	if( debug >= 4 )
		Genode::log( "  fillrect-Gradient (", memoryCanvas?"OFFscreen":"screen", "): local", stringFor(rect_local).String() );//, stringFor(color).String() );
	
	// delegate to FillPolygon(), which knows how to paint "shades" ("gradients"):
	
	//xx work-around for Polygon::Shaded_painter eating up the last pixel down (and sometimes right), as seen in the Client-Side Decorator, painting the yellow(ish) tab:
	const BPoint ytweak( 0., 1. );
	const BPoint xtweak( 1., 0. );
	BPoint a[4] = {
			rect_local.LeftTop(), rect_local.RightTop() +xtweak,
			rect_local.RightBottom() +ytweak +xtweak, rect_local.LeftBottom() +ytweak
			};
	BPolygon polygon( a, B_COUNT_OF(a) );
	
	FillPolygon( polygon, a, gradient );
}


void CanvasGenode::FillRect( BRect rect_local, rgb_color color )
{
	if( debug >= 4 )
		Genode::log( "  fillrect (", memoryCanvas?"OFFscreen":"screen", "): local ", stringFor(rect_local).String(), stringFor(color).String() );
	if( debug >= 5 )
		clipRect.PrintToStream();
	
	if( memoryCanvas )
	{
		// Off-screen FillRect() painting is just used in e.g. (our slightly customized) "Clock" applet; let's keep things simple:
		for( int x = rect_local.left; x <= rect_local.right; x += 1 )
			StrokeLine(
				BPoint( x, rect_local.top ),
				BPoint( x, rect_local.bottom),
				color
				);
	}
	else  // screenCanvas
	{
		// convert to framebuffer coords:
		const BRect rect = rect_local
			.OffsetByCopy( currentOrigin );
		
		Genode::Surface<PT>
			surface { screenCanvas->local_addr<PT>(), _size };
		
		surface.clip( Rect(
			Point( clipRect.left, clipRect.top ),
			Area( clipRect.Width() +1, clipRect.Height() +1 )
			) );
		
///ToDo: trouble with AutoCast transparency: seems Box.cpp is to blame, and not this, which handles alpha-blending/transparency well ? Confirm, and then remove this comment
		Box_painter::paint(
			surface,
			Rect(
				Point( rect.left, rect.top ),
				Area( rect.Width() +1, rect.Height() +1 )
				),
			Genode::Color( color.red, color.green, color.blue, color.alpha )
			);
	}
}


void CanvasGenode::StrokeRect( BRect rect_local, rgb_color color )
{
	//Genode::log( "  strokrect: ", stringFor(rect_local).String() );
	StrokeLine( rect_local.LeftTop(),     rect_local.RightTop(), color );
	StrokeLine( rect_local.RightBottom(), rect_local.RightTop(), color );
	StrokeLine( rect_local.RightBottom(), rect_local.LeftBottom(), color );
	StrokeLine( rect_local.LeftTop(),     rect_local.LeftBottom(), color );
}



static
void getCornersFor(
	const BRect rect_local,
	const float xrad,
	const float yrad,
	BRect & nw,
	BRect & ne,
	BRect & se,
	BRect & sw
	)
{
	// North-West corner
	{
		BRect r( rect_local );
		r.right = r.left + xrad;  /// xx should substract 1 pixel..?
		r.bottom = r.top + yrad;
		
		nw = r;
	}
	
	// NE corner
	{
		BRect r( rect_local );
		r.left = r.right - xrad;
		r.bottom = r.top + yrad;
		
		ne = r;
	}
	
	// SE corner
	{
		BRect r( rect_local );
		r.left = r.right - xrad;
		r.top = r.bottom - yrad;
		
		se = r;
	}
	
	// SW corner
	{
		BRect r( rect_local );
		r.right = r.left + xrad;
		r.top = r.bottom - yrad;
		
		sw = r;
	}
}


void CanvasGenode::StrokeRoundRect( BRect rect_local, float xrad, float yrad, rgb_color color )
{
	if( debug >= 4 )
		Genode::log( "  strokeRoundRect: local ", stringFor(rect_local).String(), stringFor(color).String() );
	
	BRect nw;
	BRect ne;
	BRect se;
	BRect sw;
	{
		getCornersFor(
			rect_local,
			xrad, yrad,
			nw, ne, se, sw
			);
	}
	
	StrokeArc(
		BRect( nw.left, nw.top, nw.right+xrad, nw.bottom+yrad ),
		90, 90, color
		);
	StrokeArc(
		BRect( sw.left, sw.top -yrad, sw.right+xrad, sw.bottom ),
		180, 90, color
		);
	StrokeArc(
		BRect( se.left -xrad, se.top -yrad, se.right, se.bottom ),
		270, 90, color
		);
	StrokeArc(
		BRect( ne.left -xrad, ne.top, ne.right, ne.bottom +yrad ),
		0, 90, color
		);
	
	StrokeLine( nw.LeftBottom(),  sw.LeftTop(), color );
	StrokeLine( sw.RightBottom(), se.LeftBottom(), color );
	StrokeLine( se.RightTop(),    ne.RightBottom(), color );
	StrokeLine( ne.LeftTop(),     nw.RightTop(), color );
}


void CanvasGenode::FillRoundRect( BRect rect_local, float xrad, float yrad, rgb_color color )
{
	if( debug >= 4 )
		Genode::log( "  fillRoundRect: local ", stringFor(rect_local).String(), stringFor(color).String() );
	
	// Lion's share:
	{
		BRect r( rect_local );
		r.InsetBy( xrad +1., 0. );
		
		FillRect( r, color );
	}
	
	// Remainder:
	
	BRect nw;
	BRect ne;
	BRect se;
	BRect sw;
	{
		getCornersFor(
			rect_local,
			xrad, yrad,
			nw, ne, se, sw
			);
	}
	
	// West sliver
	{
		BRect r( nw );
		r.top = r.bottom +1.;
		r.bottom = sw.top -1.;
		
		FillRect( r, color );
	}
	
	// East sliver
	{
		BRect r( ne );
		r.top = r.bottom +1.;
		r.bottom = se.top -1.;
		
		FillRect( r, color );
	}
	
	// Corners
	FillArc(
		BRect( nw.left, nw.top, nw.right+xrad, nw.bottom+yrad ),
		90, 90, color
		);
	FillArc(
		BRect( sw.left, sw.top -yrad, sw.right+xrad, sw.bottom ),
		180, 90, color
		);
	FillArc(
		BRect( se.left -xrad, se.top -yrad, se.right, se.bottom ),
		270, 90, color
		);
	FillArc(
		BRect( ne.left -xrad, ne.top, ne.right, ne.bottom +yrad ),
		0, 90, color
		);
}



//#pragma mark - ellipses/arcs -


void CanvasGenode::StrokeEllipse( BRect rect_local, rgb_color color )
{
	if( debug >= 5 )
		Genode::log( "  StrokeEllipse, local ", stringFor(rect_local).String(), stringFor(color).String() );
	
	int width = rect_local.Width() /2;
	int height = rect_local.Height() /2;
	BPoint origin_local(
		(rect_local.left + rect_local.right) /2.,
		(rect_local.top + rect_local.bottom) /2.
		);
	
	// T0
	doBoundedArc( -width, width, -height, height, false, color, width, height, origin_local );
	
	if( debug >= 9 )
		Genode::log( "  ~StrokeEllipse done" );
}

void CanvasGenode::FillEllipse( BRect rect_local, rgb_color color )
{
	if( debug >= 5 )
		Genode::log( "  FillEllipse, local ", stringFor(rect_local).String(), stringFor(color).String() );
	
	//const BRect rect = rect_local.OffsetByCopy( dX, dY );
	//Genode::log( "  FillEllipse,       p1 ltrb: ", rect.left, " , ", rect.top, " , ", rect.right, " , ", rect.bottom );
	
	int width = rect_local.Width() /2;
	int height = rect_local.Height() /2;
	BPoint origin_local(
		(rect_local.left + rect_local.right) /2.,
		(rect_local.top + rect_local.bottom) /2.
		);
	
	doBoundedArc( -width, width, -height, height, true, color, width, height, origin_local );
	
	if( debug >= 9 )
		Genode::log( "  ~FillEllipse done" );
}


void CanvasGenode::doBoundedArc(
			int x0,
			int x1,
			int y0,
			int y1,
			bool fill,
			rgb_color color,
			int width,
			int height,
			BPoint origin_local
			)
{
	if( debug >= 6 )
		Genode::log( "   doArc ", x0, ",", x1, ",", y0, ",", y1, " width=", width, " height=", height );
	
	// This variant does not know about angles, but may 'clip' to a rect (typically a quarter-sector).
	// Used for full ellipses.
	///ToDo-2: we no longer use the "clipping" feature, either clean-up, or even consolidate all onto doArc_allQuadrants()
	
	// we don't use surface.clip clipping, so check like this instead:
	if( false == clipRect.IsValid() )
		//
		return;
		//
	
	// https://stackoverflow.com/questions/10322341/simple-algorithm-for-drawing-filled-ellipse-in-c-c
	
	for( int y = y0; y <= y1; y++ )
	{
		for( int x = x0; x <= x1; x++ )
		{
			const int diff = x*x*height*height+y*y*width*width - height*height*width*width;
			const bool on_ellipse = fill
				? diff <= 0  // Select *all* pixels inside the elliptic bound
				: abs(diff) <= width*height*4*4;  // Select a (somewhat thick) border/bound (value found by trial&error).
				;
			
			if( on_ellipse )
				setPixel_LocalCoords(
					origin_local.x +x,
					origin_local.y +y,
					color
					);
		}
	}
}



void CanvasGenode::StrokeArc( BRect rect_local, float start_angle, float arc_angle, rgb_color color )
{
	if( debug >= 5 )
		Genode::log( "  StrokeArc, local ", stringFor(rect_local).String(), stringFor(color).String(), " start: ", start_angle, " span: ", arc_angle );
	
	doArc_allQuadrants( false, rect_local, start_angle, arc_angle, color );
}

void CanvasGenode::FillArc( BRect rect_local, float start_angle, float arc_angle, rgb_color color )
{
	if( debug >= 5 )
		Genode::log( "  FillArc (", start_angle, ", ", arc_angle, "), local ", stringFor(rect_local).String(), stringFor(color).String(), " start: ", start_angle, " span: ", arc_angle );
	
	doArc_allQuadrants( true, rect_local, start_angle, arc_angle, color );
}


void CanvasGenode::doArc_allQuadrants(
			bool fill,
			BRect rect_local,
			float start_angle,
			float arc_angle,
			rgb_color color
			)
{
	// This variant (and helper method down there) can paint partial ellipses, i.e. arcs.
	// Used for StrokeArc() and FillArc().
	
	if( arc_angle <= 0. )
	{
		Genode::warning( "negative arc_angle, bailing out" );
		//
		return;
	}
	
	int width = rect_local.Width() /2;
	int height = rect_local.Height() /2;
	BPoint origin_local(
		(rect_local.left + rect_local.right) /2.,
		(rect_local.top + rect_local.bottom) /2.
		);
	
	{
		float end_angle = start_angle + arc_angle;
		if( end_angle < start_angle )
			Genode::warning( "doArc: negative start angle not implemented/tested" );
		
		if( start_angle < 90 )
			// there is something to paint in the NE quadrant
			doArc_Quadrant( 1, fill, color, width, height, origin_local, start_angle, Genode::min(end_angle, 90) );
		//width -= 4; height -= 4; color.green += 60;  // -> visual debugging help...
		
		if( start_angle < 180 )
			// there is something to paint in the NW quadrant
			doArc_Quadrant( 2, fill, color, width, height, origin_local, Genode::max(start_angle, 90), Genode::min(end_angle, 180) );
		//width -= 4; height -= 4; color.green += 60;
		
		if( start_angle < 270 )
			// there is something to paint in the SW quadrant
			doArc_Quadrant( 3, fill, color, width, height, origin_local, Genode::max(start_angle, 180), Genode::min(end_angle, 270) );
		//width -= 4; height -= 4; color.green += 60;
		
		if( start_angle < 360 )
			// there is something to paint in the SE quadrant
			doArc_Quadrant( 4, fill, color, width, height, origin_local, Genode::max(start_angle, 270), Genode::min(end_angle, 360) );
	}
}


void CanvasGenode::doArc_Quadrant(
			int quadrant,
			bool fill,
			rgb_color color,
			const int width,/// or wid_radius ?
			const int height,
			BPoint origin_local,
			float start_angle,
			float end_angle  // !
	)
{
	if( end_angle <= start_angle )
		//
		return;
		//
	
	if( debug >= 6 )
		Genode::log( "   doArc-bis  ", start_angle, "° --> ", end_angle, "°  width ", width, "  height ", height, "  x:y ", origin_local.x, ":", origin_local.y, "  quadrant ", quadrant );
	
	// Called by:
	// doArc_allQuadrants() calls this 4 times with each "quarter" of an ellipse.
	// Indeed our algorith calculates "left of angled line.. " and "right of.." which only makes sense
	// if passed lines angled between 0 and 90° sectors.
	//
	// Outline:
	// - we want to delimitate the arc between two lines (at starting and ending angle), aka "spokes"
	// - do the trigo to convert spokes' angles to cartesian coords of 'begin' and 'arrival' points (from ellipse center to border)
	// - from the coords of the 'arrival' point, we can deduce a line equation a.x+b, which actually is simplified to a.x since the center coords are normalized to (0, 0).
	// - then we run a "foreach pixel" loop, checking each pixel against all 3 constraints (ellipse bounds, spoke 1, spoke 2).
	
	// pre-requisites:
	// - Line1 angle in [     x° .. 90° * quadrant ]
	// - Line2 angle in [ Line1° .. Line1 + 90° ]
	
	float degrees1 = start_angle;
	float degrees2 = end_angle;
	/// ASSERT b in [0, 360] ?
	
	// Do the trig to convert from polar to cartesian, for spoke 1 and spoke 2
	// BAffineTransform handles all the cos() and sin() for us, so long as it's passed radians instead of degrees.
	//
	const BPoint origin( 0., 0 );
	BPoint dest1, dest2;
	{
		const BPoint axis_zero = BPoint( height+width, 0. );  // The axis' length does not matter, as long as it is bigger than height or width, so adding them up fits the bill
		//axis_zero.PrintToStream();  // e.g.: BPoint(x:60, y:0)
		
		const float radians1 = degrees1 * M_PI / 180.;
		const float radians2 = degrees2 * M_PI / 180.;
		dest1 = BAffineTransform::AffineRotation( radians1 ).Apply( axis_zero );
		dest2 = BAffineTransform::AffineRotation( radians2 ).Apply( axis_zero );
	}
	//dest1.PrintToStream();  // e.g.: BPoint(x:52, y:30), after a 30° rotation
	//dest2.PrintToStream();
	
	// For spoke 1 and 2 : are they closer to horizontal or closer to vertical?
	const bool vert_spoke1 = abs(dest1.y) >= abs(dest1.x);
	const bool vert_spoke2 = abs(dest2.y) >= abs(dest2.x);
	
	if( debug >= 6 )
		Genode::log( "vert_spoke1: ", vert_spoke1, " vert_spoke2: ", vert_spoke2 );
	
	// Since origin is always (0, 0) this simplifies the line's equation.
	// Depending on which side of 45° it's on, the equation is either
	// -->  y = factor * x  (if angle is between 45° and 135°)
	// -->  x = factor * y  (if angle is in 0..45 or in 135..180°)
	// Where
	// -->  factor = dx/dy (or dy/dx)
	
	// factor, aka *slope*
	auto slope_for = [&] ( float d1, float d2 )
		{
			const float epsilon = 0.01;
			if( abs(d2) < epsilon )
				return 0.;  // dodge divide-by-zero below
			
			double res = //double( abs(d1) ) / double( abs(d2) )
				double( d1 ) / double( d2 )
				;
			
			// convert "-0.00" to "0.00" (otherwise our later calculations go "upside down")
			if( res < 0. && res > -0.1 )
				res = 0.;
			
			return res;
		}
		;
	///or do int-fraction math instead of float?
	float slope1X = slope_for( dest1.y, dest1.x );
	float slope1Y = slope_for( dest1.x, dest1.y );
	;
	float slope2X = slope_for( dest2.y, dest2.x );
	float slope2Y = slope_for( dest2.x, dest2.y );
	
#if 0
	BString s;
	s << "Slope factors: ";
	if( vert_spoke1 )
		s << slope1X << " [" << slope1Y << "]";
	else
		s << "[" << slope1X << "] " << slope1Y;
	
	s << "  ";
	
	if( vert_spoke2 )
		s << slope2X << " [" << slope2Y << "]";
	else
		s << "[" << slope2X << "] " << slope2Y;
	
	Genode::log( s.String() );
	//Genode::log( "Slope factors: ", slope1X, " ", slope1Y, " ", slope2X, " ", slope2Y, " " );
#endif
	
	auto do_xy = [&] ( int x, int y )
		{
			{
				const int diff = x*x*height*height+y*y*width*width - height*height*width*width;
				const bool on_ellipse = fill
					? diff <= 0  // Select *all* pixels inside the elliptic bound
					: abs(diff) <= width*height*25;  // Select a (somewhat thick) border/bound (value found by trial&error).
					;
				
				bool beyond_spoke_1 = false;
				bool beyond_spoke_2 = false;
				switch( quadrant )
				{
					// Implementation detail: to avoid "off by one" errors (missing pixels)
					// one has to be careful with "<" and "<=" tests here.
					// The latter are problematic, as  " 0. <=  -0." does not test as true (!),
					// i.e. negative zero (!) is not the same as positive zero... So it's better
					// to test for " x < y+1. " instead.
					
					case 1:  // NE quadrant
						beyond_spoke_1 = vert_spoke1
							? x <= float(y) * slope1Y
							: y >= float(x) * slope1X ;
						beyond_spoke_2 = vert_spoke2
							? x <  float(y) * slope2Y
							: y >= float(x) * slope2X ;
					break;
					case 2:  // NW quadrant
						beyond_spoke_1 = vert_spoke1
							? x <= float(y) * slope1Y
							: y <= float(x) * slope1X ;
						beyond_spoke_2 = vert_spoke2
							? x <= float(y) * slope2Y
							: y <= float(x) * slope2X ;
					break;
					case 3:  // SW quadrant
						beyond_spoke_1 = vert_spoke1
							? x >= float(y) * slope1Y
	///ToDo: there remains a few "off by one" missing pixels, though less visible than that glaring one fixed here : find them and apply the same "-1" fix
							: y-1 <  float(x) * slope1X ;  // test for strict inferiority to the next pixel (-1) instead of inferior-or-equal to the wanted pixel, as that latter test is not reliable
						beyond_spoke_2 = vert_spoke2
							? x >  float(y) * slope2Y
							: y <  float(x) * slope2X ;
					break;
					case 4:  // SE quadrant
						beyond_spoke_1 = vert_spoke1
							? x >= float(y) * slope1Y      // select X'es *to the right* of the (kinda-vertical) spoke
							: y >= float(x) * slope1X ;    // select Y'es *above* the (kinda-horizontal) spoke
						beyond_spoke_2 = vert_spoke2
							? x >= float(y) * slope2Y
							: y >= float(x) * slope2X ;
					break;
				}
				
				// select pixels that fill (or stroke, depending) the ellipse,
				// whose angular position is beyond spoke1, but *before* spoke2:
				//
				if( on_ellipse && beyond_spoke_1 && false == beyond_spoke_2 )
					setPixel_LocalCoords(
						origin_local.x +x,
						origin_local.y -y,  // *Minus* y : do Y mirroring (convert from cartesian coordinates to screen coordinates) here at the last step
						color
						);
			}
		}
		;
	
	for( int y = -height; y <= height; y++ )
	{
		for( int x = -width; x <= width; x++ )
		{
			do_xy( x, y );
		}
	}
}



//#pragma mark - string/bitmap -


void CanvasGenode::DrawString( const char * raw_text, int length, BPoint where_local, rgb_color color )
{
	if( debug >= 4 )
		Genode::log( "   DrawString: ", where_local.x, " , ", where_local.y, stringFor(color).String(), " : ", raw_text );
	
///later: implement offscreen / DrawString
	if( memoryCanvas )
		//
		return;
		//
	
	const BPoint where =
		where_local + currentOrigin
		;
	
	Genode::Surface<PT>
		surface { screenCanvas->local_addr<PT>(), _size };
	
	surface.clip( Rect(
		Point( clipRect.left, clipRect.top ),
		Area( clipRect.Width() +1, clipRect.Height() +1 )
		) );
	
///ToDo: DrawString() params: pass BFont/family/ID as param, or outright the full *BView.viewState* itself
	const int font_id = 0;
	const Text_painter::Font & font =
		*be_app->GenoFonts().Font( font_id ).tpainterFont
		;
	
	{
		// do apply the length, otherwise BTextView displays junk (it passes us strings that are *not* nul-terminated)
		char * text = (char*)malloc( length +sizeof('\0') );
		strlcpy( text, raw_text, length +sizeof('\0') );
		text[ length ] = '\0';
		
		if( debug >= 5 )
			Genode::log( "   ->drawString <", raw_text, ">: substracting ", font.baseline(), " from where_local.y=", where_local.y );
		
		Text_painter::paint(
			surface,
			Text_painter::Position(
				where.x,
				where.y - font.baseline()  // seems Genode works the opposite of Haiku, handles y coord relative to top instead of relative to baseline..
				),
			font,
			Genode::Color( color.red, color.green, color.blue, color.alpha ),
			text
			);
		
		free( text );
	}
}



static
size_t get_raw_bytes_per_row( color_space color_space, int32 width )
{
	// We use a custom-made get_raw_bytes_per_row() rather than haiku's, as the latter aligns
	// to 32bit, whereas drawbitmap-to-genode-surface below does *not* align to 32 bits it seems.
	
	switch( color_space )
	{
		case B_RGB32:
		case B_RGBA32:
			return 4 * width;
		break;
		
		case B_RGB24:
			return 3 * width;
		break;
		
		case B_RGB16:
			return 2 * width;
		break;
		
		default:
			;
	}
	
	Genode::error("Canvas: get_raw_bytes_per_row: unimplemented color space");
	return width;
}


void CanvasGenode::Bitmap_painter(
		const BPoint where,
		Genode::Surface<PT> & surface,
		const BBitmap & bitmap,
		const BRect source_rect
		)
{
	typedef Genode::Surface_base::Rect  Rect;
	
	// initial (un-chopped) bitmap rect
	const BRect r( bitmap.Bounds() );
	
	BSize dest_size;///ToDo: should be passed as a parameter instead of hardcoding dest_size:=source_size....
	{
		dest_size.Set(
			source_rect.IntegerWidth() +1,
			source_rect.IntegerHeight() +1
			);
	}
	
	// chopped (clipped) bitmap rect:
	BRect raw(
		where,
		where + BPoint( dest_size.IntegerWidth() -1, dest_size.IntegerHeight() -1 )
		);
	Rect clipped = Rect::intersect(
		surface.clip(),
		Rect(
			Genode::Surface_base::Point( where.x, where.y ),
			Genode::Surface_base::Area( source_rect.IntegerWidth() +1, source_rect.IntegerHeight() +1 )
			)
		);
	
	// coords: *relative* to bitmap (i.e. 0-0: no chopping ; 1-1: chop first line and first column ; and so on and so forth)
	BPoint chopping;
	float chop_left = 0.;
	float chop_right = 0.;
	float chop_top = 0.;
	float chop_bottom = 0.;
	{
		chopping =
			BPoint( clipped.x1(), clipped.y1() )
			- where//pos
			;
		chopping.ConstrainTo( BRect(0., 0., 9999., 9999.) );
		
		chop_left = chopping.x;
		chop_top  = chopping.y;
		
		chop_right = raw.right - clipped.x2();
		if( chop_right < 0 )
			chop_right = 0;
		
		chop_bottom = raw.bottom - clipped.y2();
		if( chop_bottom < 0 )
			chop_bottom = 0;
	}
	
	if( debug >= 3 )
	{
		raw.PrintToStream();
		Genode::log( "  ..Bitmap-painter @ ", where.x, ":", where.y,//position,//.x(), " ", position.y(),
			" surface-clip: ", surface.clip(),
			" intersection: ", clipped,
			" chop-left: ", chop_left,
			" chop-right: ", chop_right
			);
	}
	
/*	if( false==clipped.valid() )
		//
		return;
		//
		*/
	
//	const int src_w = bitmap.Bounds().IntegerWidth() +1;
//	const int dst_w = surface.size().w();  // destination stride (-> window width, not bitmap width, so potentially much bigger than <src_w> !)
	
	// input: byte offset (within source texture)
	unsigned long tex_start_offset = 0
//		+ (clipped.y1() - position.y())*src_w
//		+  clipped.x1() - position.x()
		;
	
	// output: byte offset + base pointer (within framebuffer)
	PT * dst =
		surface.addr()
//		+ clipped.y1()*dst_w
//		+ clipped.x1()
		;  // <clipped> represents the "view/painting port" inside the destination framebuffer
	
	BPoint src_where;
	{
		src_where = source_rect.LeftTop();
		//+? if( src_where to the top and left of <chopping> )
		{
			src_where += chopping;
		}
	}
	
	BPoint dest_where;  // aka "composite_of_user-requested-position_and_clipping"
	{
		dest_where = where;
		//+? if( src_where to the top and left of <chopping> )
		{
			dest_where += chopping;
		}
	}
	
	if( debug >= 4 )
		Genode::log(
			"   paint bitmap to Surface: ", surface.size(),
			" with stride dst_w=", surface.size().w,
			" src_where: ", int(src_where.x), " ", int(src_where.y),
			" dest_where: ", int(dest_where.x), " ", int(dest_where.y)
			); // note: beside surface.size(), surface.clip() is interesting, it has an offset, denoting the view "port" inside dest window..
	
	switch( bitmap.ColorSpace() )
	{
		case B_CMAP8:
		case B_RGB24:
		case B_RGB32:
		{
#if 0
			B_CMAP8--> :
			//BPrivate::PaletteConverter pal;
			//pal.InitializeDefault( false );
			//Genode::warning("pal status: ", pal.InitCheck());
			//fails to initialize for some reason.. so create and use a global accessor "Palette_RGBColorForIndex":
			int i = 0;
			int j = 0;
			PT * d = NULL;
			const uint8 * s = NULL;
			
			for (j = clipped.h(); j--; src += src_w, dst += dst_w)
			{
				for (i = clipped.w(), s = src, d = dst; i--; s++, d++)
				{
					
					rgb_color col = BPrivate::Palette_RGBColorForIndex( *s );
					*d = PT( col.red, col.green, col.blue );
					//if(j==0) Genode::log( col.red, " ", col.green, " ", col.blue, "  from index-cmap8: ", *s );
				}
			}
#else
			const uint8 * src = (const uint8*)bitmap.Bits() + tex_start_offset;
			
			const color_space
				nitpicker_colspace = B_RGB32;  // Haiku's equivalent for <PT> Genode::Pixel_rgb888
			
			BPrivate::ConvertBits(
				src,  // initial byte of texture
				dst,  // initial byte of surface output
				bitmap.BitsLength(),
				get_raw_bytes_per_row( nitpicker_colspace, surface.size().w ) *surface.size().h,// *clipped.h(),
				bitmap.BytesPerRow(),
				get_raw_bytes_per_row( nitpicker_colspace, surface.size().w ),  // B_RGB16 takes 2 bytes per pixel
				bitmap.ColorSpace(),
				nitpicker_colspace,
#if 0
				BPoint( 0,0),//clipped.x1(), clipped.y1() ),//0., 0. ),  // offset at which to start reading
				BPoint( position.x(), position.y() ),  // offset at which to start writing
				bitmap.Bounds().IntegerWidth() +1,//clipped.w(),  // The width (in pixels) to convert.
				bitmap.Bounds().IntegerHeight() +1//clipped.h()  // The height (in pixels) to convert.
#else
				src_where,
				dest_where,//BPoint( position.x(), position.y() ),  // offset at which to start writing
				dest_size.IntegerWidth() - chop_right - chop_left,  // Chop after this excess width : The width (in pixels) to convert.
				dest_size.IntegerHeight()- chop_top - chop_bottom   // Chop excess height : The height (in pixels) to convert.
#endif
				);
#endif
		}
		break;
		
		default:
			///ToDo: we get "color space 8200 n/i" all the time from *Deskbar*, esp. when opening its main menu... and from BFilePanel, see the last few lines of "jam t8", after "tracker: cannot load icon rsrc 1021", which then paints a 16.0x16.0 bitmap (probably the "folder" mini icon next to the [/boot] BFieldMenu ?
			Genode::warning("Canvas.drawbitmap: color space ", int(bitmap.ColorSpace()), " not yet implemented!" );
	}
	
	// T+
	surface.flush_pixels( clipped );
	
	if( debug >= 5 )
		Genode::log( "leaving Bitmap-painter......." );
}


void CanvasGenode::DrawBitmap(
			const BBitmap * bitmap,
			const BRect bm_chopping,
			const BPoint where_local
			)
{
///ToDo: clipping: implement complex BRegion clipping ? Genode/Surface implements rect clipping only...
// the reason for this used to be visible in Clock: in Clock, due to out-of-band drawing, were clobberring the BDragger (lower-right corner) as its own drawing is never called to make it "re-appear" on top of its being erased, in out-of-band situations..
// but now Invalidate(smart-rect-here) is called (and enforced) so it hides the problem.. But said problem will probably resurface elsewhere.
///E.G.: maybe SetViewColor() also would benefit from complex BRegion clipping, and lack of it is the reason why AC's background is hidden behind a gray blob...?
	///+assert( bitmap && bitmap->Bits() && bitmap->Width()... )
	
	if( debug >= 3 )
	{
		const BRect
			bounds( bitmap->Bounds() );
		Genode::log( "  drawbitmap (", (memoryCanvas?"OFFscreen":"videobuffer"), ") ColorSpace ", int32(bitmap->ColorSpace()), ", local-where (", int(where_local.x), " ", int(where_local.y), ")  bmap: ", bounds.Width()+1, "x", bounds.Height()+1, " @", bitmap, "  currentOrigin: ", currentOrigin.x, " ", currentOrigin.y );
		switch( bitmap->ColorSpace() )
		{
			case B_RGB32:
				//Genode::log( "(B_RGB32)" );  // used by AutoCast's backghround.jpeg, etc
			break;
			case B_RGB24:
				//Genode::log( "(B_RGB24)" );
			break;
			case B_CMAP8:
				//Genode::log( "(B_CMAP8)" );  // used by Clock, Pulse, etc
			break;
			
			default:
				Genode::log( "colorspace (? ? ?)" );
				;
		}
	}
	
	const BPoint where =
		where_local + currentOrigin
		;
	
	if( memoryCanvas )
	{
///ToDo-2: implement offscreen / DrawBitmap (can't test yet with Clock though, since support for its bitmap resources is not yet implemented, so DrawBitmap(rsrc) is not even called by clock)
///ToDo: offscreen / DrawBitmap: for now, call FillRect( bitmap->Bounds(), where_local ) temporarily ? Then I can discard the FillRect() patch on Clock's cl_view.cpp ...
		// paint bitmap
		////switch( offscreenCanvas->ColorSpace.. )
		//Genode::Pixel_alpha8 * pixels = (Genode::Pixel_alpha8*)
		//	memoryCanvas->Bits();
		//
		//..
	}
	else  // screenCanvas:
	{
		Genode::Surface<PT>
			surface { screenCanvas->local_addr<PT>(), _size };
		
		surface.clip( Rect(
			Point( clipRect.left, clipRect.top ),
			Area( clipRect.Width() +1, clipRect.Height() +1 )
			) );
///later: BBitmap zooming support ..etc (see gems/src/app/backdrop/main.cc ?)
		Bitmap_painter(
			where,
			surface,
			*bitmap,
			bm_chopping  // might be either the unchopped bitmap.Bounds(), or a chopped subset of the bm (if the special-case BView.DrawBitmap() API is called)
			);
	}
}




