public CCtrl *CtrlFindUnique(CTask *haystack_task, I64 needle_type)
{//Find task ctrl given ctrl_type.
    CCtrl *c;

    c = haystack_task->next_ctrl;
    while (c != &haystack_task->next_ctrl)
    {
        if (c->type == needle_type)
            return c;
        c = c->next;
    }
    return NULL;
}

U0 CtrlsUpdate(CTask *task)
{
    CCtrl *c;

    c = task->next_ctrl;
    while (c != &task->next_ctrl)
    {
        if (c->update_derived_vals)
            (*c->update_derived_vals)(c);
        if (c->flags & CTRLF_BORDER)
        {
            c->screen_left   = gr.pan_text_x + task->pix_left + c->left   - FONT_WIDTH;
            c->screen_right  = gr.pan_text_x + task->pix_left + c->right  - FONT_WIDTH;
            c->screen_top    = gr.pan_text_y + task->pix_top  + c->top    - FONT_HEIGHT;
            c->screen_bottom = gr.pan_text_y + task->pix_top  + c->bottom - FONT_HEIGHT;
        }
        else
        {
            c->screen_left   = gr.pan_text_x + task->pix_left + c->left;
            c->screen_right  = gr.pan_text_x + task->pix_left + c->right;
            c->screen_top    = gr.pan_text_y + task->pix_top  + c->top;
            c->screen_bottom = gr.pan_text_y + task->pix_top  + c->bottom;
        }
        c = c->next;
    }
}

fp_update_ctrls = &CtrlsUpdate;

Bool CtrlInsideRect(CCtrl *c, I64 x, I64 y)
{//screen coordinates
    if (c->screen_left <= x <= c->screen_right && c->screen_top <= y <= c->screen_bottom)
        return TRUE;
    else
        return FALSE;
}

public Bool CtrlInside(CCtrl *c, I64 x, I64 y)
{//Is x, y inside a ctrl?
    if (c->flags & CTRLF_SHOW)
    {
        if (c->inside_ctrl)
            return (*c->inside_ctrl)(c, x, y);
        else
            return CtrlInsideRect(c, x, y);
    }
    else
        return FALSE;
}

U0 DrawCtrls(CTask *task)
{
    CCtrl *c;
    CDC   *dc = DCAlias(gr.dc2, task);

    c = task->next_ctrl;
    while (c != &task->next_ctrl)
    {
        if (c->flags & CTRLF_SHOW)
        {
            if (c->flags & CTRLF_BORDER)
            {
                if (!Bt(&task->display_flags, DISPLAYf_NO_BORDER))
                {
                    PUSHFD
                    CLI
                    while (LBts(&task->task_flags, TASKf_TASK_LOCK))
                        PAUSE

                    task->win_left--; //Allow drawing on border
                    task->win_right++;
                    task->win_top--;
                    task->win_bottom++;
                    WinDerivedValsUpdate(task);

                    LBtr(&task->task_flags, TASKf_TASK_LOCK);
                    POPFD

                    if (c->draw_it)
                        (*c->draw_it)(dc, c);

                    PUSHFD
                    CLI
                    while (LBts(&task->task_flags, TASKf_TASK_LOCK))
                        PAUSE

                    task->win_left++;
                    task->win_right--;
                    task->win_top++;
                    task->win_bottom--;
                    WinDerivedValsUpdate(task);

                    LBtr(&task->task_flags, TASKf_TASK_LOCK);
                    POPFD
                }
            }
            else
                if (c->draw_it)
                    (*c->draw_it)(dc, c);
        }
        c = c->next;
    }
    DCDel(dc);
}

#define WIN_SCROLL_SIZE         8
#define WIN_SCROLL_BORDER_BONUS 4
U0 DrawWinScroll(CDC *dc, CCtrl *c)
{
    CWinScroll *s = c->state;

    if (c->flags & CTRLF_CLICKED)
        dc->color = s->color >> 4;
    else
        dc->color = s->color & 0xF;
//  GrRect(dc, c->left, c->top, c->right - c->left + 1, c->bottom - c->top + 1);
    GrPutS(dc, c->left, c->top, ".");

    if (c->flags & CTRLF_CLICKED)
        dc->color = s->color & 0xF;
    else
        dc->color = s->color >> 4;
//  GrRect(dc, c->left + 2, c->top + 2, c->right - c->left + 1 - 4, c->bottom - c->top + 1 - 4);
    GrPutS(dc, c->left, c->top, ".");

}

U0 WinDerivedScrollValsUpdate(CCtrl *c)
{
    CWinScroll *s = c->state;
    I64         range;

    if (s->max < s->min) s->max = s->min;
    if (s->pos < s->min) s->pos = s->min;
    if (s->pos > s->max) s->pos = s->max;

    s->color = c->win_task->border_attr & 0xF ^ 0xF + (c->win_task->border_attr & 0xF) << 4;
    range = s->max - s->min;
    if (!range)
        range = 1;
    switch (c->type)
    {
        case CTRLT_WIN_HSCROLL:
            c->left   =  gr.pan_text_x + FONT_WIDTH - WIN_SCROLL_BORDER_BONUS +
                         (s->pos - s->min) * (c->win_task->pix_width + 2 * WIN_SCROLL_BORDER_BONUS - WIN_SCROLL_SIZE) / range;
            c->right  =  c->left + WIN_SCROLL_SIZE - 1;
            c->top    =  gr.pan_text_y + FONT_HEIGHT + (FONT_WIDTH - WIN_SCROLL_SIZE) / 2 + c->win_task->pix_height;
            c->bottom =  c->top + WIN_SCROLL_SIZE - 1;
            break;
        case CTRLT_WIN_VSCROLL:
            c->left   = gr.pan_text_x + FONT_WIDTH + (FONT_WIDTH - WIN_SCROLL_SIZE) / 2 + c->win_task->pix_width;
            c->right  = c->left + WIN_SCROLL_SIZE - 1;
            c->top    = gr.pan_text_y + FONT_HEIGHT - WIN_SCROLL_BORDER_BONUS+
                        (s->pos - s->min) * (c->win_task->pix_height + 2 * WIN_SCROLL_BORDER_BONUS - WIN_SCROLL_SIZE) / range;
            c->bottom = c->top + WIN_SCROLL_SIZE - 1;
            break;
    }
}

U0 LeftClickHWinScroll(CCtrl *c, I64 x, I64, Bool down)
{
    CTask       *task = c->win_task;
    CWinScroll  *s = c->state;
    I64          range = task->pix_width + 2 * WIN_SCROLL_BORDER_BONUS - WIN_SCROLL_SIZE;

    LBts(&s->flags, WSSf_SET_TO_POS);
    s->pos = ((x - (FONT_WIDTH - WIN_SCROLL_BORDER_BONUS))
    *(s->max - s->min + 1) + range / 2) / range + s->min;
    if (down)
        c->flags |= CTRLF_CLICKED;
    else
        c->flags &= ~CTRLF_CLICKED;
    if (c->update_derived_vals)
        (*c->update_derived_vals)(c);
}

U0 LeftClickVWinScroll(CCtrl *c, I64, I64 y, Bool down)
{
    CTask       *task = c->win_task;
    CWinScroll  *s = c->state;
    I64          range = task->pix_height + 2 * WIN_SCROLL_BORDER_BONUS - WIN_SCROLL_SIZE;

    LBts(&s->flags, WSSf_SET_TO_POS);
    s->pos = ((y - (FONT_HEIGHT - WIN_SCROLL_BORDER_BONUS))
    *(s->max - s->min + 1) + range / 2) / range + s->min;
    if (down)
        c->flags |= CTRLF_CLICKED;
    else
        c->flags &= ~CTRLF_CLICKED;
    if (c->update_derived_vals)
        (*c->update_derived_vals)(c);
}

U0 WheelChangeWinScroll(CCtrl *c, I64 delta)
{
    CWinScroll *s = c->state;

    LBts(&s->flags, WSSf_SET_TO_POS);
    s->pos += delta;
    if (c->update_derived_vals)
        (*c->update_derived_vals)(c);
}

U0 WinScrollsInit(CTask *task)
{
    CCtrl *c;

    if (!CtrlFindUnique(task, CTRLT_WIN_HSCROLL))
    {
        c = CAlloc(sizeof(CCtrl));
        c->win_task             = task;
        c->flags                = CTRLF_SHOW | CTRLF_BORDER | CTRLF_CAPTURE_LEFT_MS;
        c->type                 = CTRLT_WIN_HSCROLL;
        c->state                = &task->horz_scroll;
        c->update_derived_vals  = &WinDerivedScrollValsUpdate;
        c->draw_it              = &DrawWinScroll;
        c->left_click           = &LeftClickHWinScroll;
        QueueInsert(c, task->last_ctrl);
    }

    if (!CtrlFindUnique(task, CTRLT_WIN_VSCROLL))
    {
        c = CAlloc(sizeof(CCtrl));
        c->win_task=task;
        c->flags                = CTRLF_SHOW | CTRLF_BORDER | CTRLF_CAPTURE_LEFT_MS;
        c->type                 = CTRLT_WIN_VSCROLL;
        c->state                = &task->vert_scroll;
        c->update_derived_vals  = &WinDerivedScrollValsUpdate;
        c->draw_it              = &DrawWinScroll;
        c->left_click           = &LeftClickVWinScroll;
        c->wheel_chg            = &WheelChangeWinScroll;
        QueueInsert(c, task->last_ctrl);
    }
    TaskDerivedValsUpdate(task);
}
#define VIEWANGLES_SPACING  22
//#define VIEWANGLES_RANGE  48
#define VIEWANGLES_RANGE    360
#define VIEWANGLES_BORDER   2
//#define VIEWANGLES_SNAP   2
#define VIEWANGLES_SNAP     1

U0 DrawViewAnglesCtrl(CDC *dc, CCtrl *c)
{
    I64          i, j;
    CViewAngles *s = c->state;

    dc->color = s->cbd;
    GrRect(dc, c->left, c->top, VIEWANGLES_SPACING * 4 + 3, VIEWANGLES_SPACING * 2 + VIEWANGLES_RANGE);
    dc->color = s->cbg;
    GrRect(dc, c->left + VIEWANGLES_BORDER, c->top + VIEWANGLES_BORDER, 
                VIEWANGLES_SPACING * 4 + 3 - 2 * VIEWANGLES_BORDER, 
                VIEWANGLES_SPACING * 2 + VIEWANGLES_RANGE - 2 * VIEWANGLES_BORDER);
    dc->color = s->config;
    GrLine(dc,  c->left + VIEWANGLES_SPACING, c->top + VIEWANGLES_SPACING, 
                c->left + VIEWANGLES_SPACING, c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE - 1);
    GrLine(dc,  c->left + 2 * VIEWANGLES_SPACING + 1, c->top + VIEWANGLES_SPACING, 
                c->left + 2 * VIEWANGLES_SPACING + 1, c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE - 1);
    GrLine(dc,  c->left + 3 * VIEWANGLES_SPACING + 2, c->top + VIEWANGLES_SPACING, 
                c->left + 3 * VIEWANGLES_SPACING + 2, c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE - 1);
    for (i = 1; i < VIEWANGLES_RANGE + 1; i += 2 * VIEWANGLES_SNAP)
    {
        j = 2 - i / 3 & 1;
        GrLine(dc,  c->left + VIEWANGLES_SPACING - j, c->bottom - VIEWANGLES_SPACING - i, 
                    c->left + VIEWANGLES_SPACING + j, c->bottom - VIEWANGLES_SPACING - i);
        GrLine(dc,  c->left + 2 * VIEWANGLES_SPACING + 1 - j, c->bottom - VIEWANGLES_SPACING - i, 
                    c->left + 2 * VIEWANGLES_SPACING + 1 + j, c->bottom - VIEWANGLES_SPACING - i);
        GrLine(dc,  c->left + 3 * VIEWANGLES_SPACING + 2 - j, c->bottom - VIEWANGLES_SPACING - i, 
                    c->left + 3 * VIEWANGLES_SPACING + 2 + j, c->bottom - VIEWANGLES_SPACING - i);
    }

    dc->color = s->cx;
    GrPrint(dc, c->left + VIEWANGLES_SPACING - FONT_WIDTH / 2, 
                c->top  + VIEWANGLES_SPACING - (1 + FONT_HEIGHT), "X");
    GrPrint(dc, c->left + VIEWANGLES_SPACING - 3 * FONT_WIDTH / 2, 
                c->top  + VIEWANGLES_SPACING + VIEWANGLES_RANGE + 3, "%3d", s->sx * 360 / VIEWANGLES_RANGE);
    i = c->left + VIEWANGLES_SPACING;
    if (s->sx > VIEWANGLES_RANGE / 2)
        j = -VIEWANGLES_RANGE / 2 + s->sx;
    else
        j = s->sx + VIEWANGLES_RANGE / 2;
    j = c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE - 1 - j;
    GrRect(dc, i - 3, j - 2, 7, 5);
    dc->color = s->cx ^ 8;
    GrRect(dc, i - 2, j - 1, 5, 3);

    dc->color = s->cy;
    GrPrint(dc, c->left + 2 * VIEWANGLES_SPACING + 1 - FONT_WIDTH / 2, 
                c->top  + VIEWANGLES_SPACING - (1 + FONT_HEIGHT), "Y");
    GrPrint(dc, c->left + 2 * VIEWANGLES_SPACING + 1 - 3 * FONT_WIDTH / 2, 
                c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE + 3, "%3d", s->sy * 360 / VIEWANGLES_RANGE);
    i = c->left + 2 * VIEWANGLES_SPACING + 1;
    if (s->sy > VIEWANGLES_RANGE / 2)
        j = -VIEWANGLES_RANGE / 2 + s->sy;
    else
        j = s->sy + VIEWANGLES_RANGE / 2;
    j = c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE - 1 - j;
    GrRect(dc, i - 3, j - 2, 7, 5);
    dc->color = s->cy ^ 8;
    GrRect(dc, i - 2, j - 1, 5, 3);

    dc->color = s->cz;
    GrPrint(dc, c->left + 3 * VIEWANGLES_SPACING + 2 - FONT_WIDTH / 2, 
                c->top  + VIEWANGLES_SPACING - (1 + FONT_HEIGHT), "Z");
    GrPrint(dc, c->left + 3 * VIEWANGLES_SPACING + 2 - 3 * FONT_WIDTH / 2, 
                c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE + 3, "%3d", s->sz * 360 / VIEWANGLES_RANGE);
    i = c->left + 3 * VIEWANGLES_SPACING + 2;
    if (s->sz > VIEWANGLES_RANGE / 2)
        j = -VIEWANGLES_RANGE / 2 + s->sz;
    else
        j = s->sz + VIEWANGLES_RANGE / 2;
    j = c->top + VIEWANGLES_SPACING + VIEWANGLES_RANGE - 1 - j;
    GrRect(dc, i - 3, j - 2, 7, 5);
    dc->color = s->cz ^ 8;
    GrRect(dc, i - 2, j - 1, 5, 3);
}

U0 UpdateDerivedViewAnglesCtrl(CCtrl *c)
{
    CViewAngles *s = c->state;

    c->left     = c->win_task->pix_width - (VIEWANGLES_SPACING * 4 + 3);
    c->right    = c->left + VIEWANGLES_SPACING * 4 + 3;
    c->top      = c->win_task->pix_height - (VIEWANGLES_SPACING * 2 + VIEWANGLES_RANGE);
    c->bottom   = c->top + VIEWANGLES_SPACING * 2 + VIEWANGLES_RANGE;
    s->sx = ClampI64(RoundI64(s->sx, VIEWANGLES_SNAP), 0, VIEWANGLES_RANGE - 1);
    s->sy = ClampI64(RoundI64(s->sy, VIEWANGLES_SNAP), 0, VIEWANGLES_RANGE - 1);
    s->sz = ClampI64(RoundI64(s->sz, VIEWANGLES_SNAP), 0, VIEWANGLES_RANGE - 1);
    s->ax = 2 * pi * s->sx / VIEWANGLES_RANGE;
    s->ay = 2 * pi * s->sy / VIEWANGLES_RANGE;
    s->az = 2 * pi * s->sz / VIEWANGLES_RANGE;
}

U0 LeftClickViewAngles(CCtrl *c, I64 x, I64 y, Bool)
{
    CViewAngles *s = c->state;
    I64          i;

    i = VIEWANGLES_RANGE - 1 - (y - (c->top + VIEWANGLES_SPACING));
    if (i >= VIEWANGLES_RANGE / 2)
        i -= VIEWANGLES_RANGE / 2;
    else
        i += VIEWANGLES_RANGE / 2;
    if (x < c->left + (c->right - c->left) / 3)
        s->sx = i;
    else if (x < c->left + 2 * (c->right - c->left) / 3)
        s->sy = i;
    else
        s->sz = i;
    if (c->update_derived_vals)
        (*c->update_derived_vals)(c);
}

public CCtrl *ViewAnglesNew(CTask *task=NULL)
{//Create view angle ctrl. See ::/Demo/Graphics/Shading.ZC.
    CCtrl       *c;
    CViewAngles *s;

    if (!task)
        task = Fs;
    if (!(c = CtrlFindUnique(task, CTRLT_VIEWING_ANGLES)))
    {
        s = CAlloc(sizeof(CViewAngles), task);
        c = CAlloc(sizeof(CCtrl));
        s->cbd                  = BLUE;
        s->cbg                  = LTBLUE;
        s->config               = BLACK;
        s->cx                   = LTGREEN;
        s->cy                   = GREEN;
        s->cz                   = LTGREEN;
        c->win_task             = task;
        c->flags                = CTRLF_SHOW | CTRLF_CAPTURE_LEFT_MS;
        c->type                 = CTRLT_VIEWING_ANGLES;
        c->state                = s;
        c->draw_it              = &DrawViewAnglesCtrl;
        c->left_click           = &LeftClickViewAngles;
        c->update_derived_vals  = &UpdateDerivedViewAnglesCtrl;
        QueueInsert(c, task->last_ctrl);
        TaskDerivedValsUpdate(task);
    }
    return c;
}

public U0 ViewAnglesDel(CTask *task=NULL)
{//Free view angle ctrl.
    CCtrl *c;

    if (!task)
        task = Fs;
    if (c = CtrlFindUnique(task, CTRLT_VIEWING_ANGLES))
    {
        QueueRemove(c);
        Free(c->state);
        Free(c);
    }
}