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.CC.
        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);
        }
}