Files
tlib/oversampling/WDL/wingui/virtwnd-listbox.cpp
2024-05-24 13:28:31 +02:00

1021 lines
29 KiB
C++

/*
WDL - virtwnd-listbox.cpp
Copyright (C) 2006 and later Cockos Incorporated
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Implementation for virtual window listboxes.
*/
#include "virtwnd-controls.h"
#include "../lice/lice.h"
#define EXPAND_SCROLLBAR_SIZE(sz) ((sz)*2)
WDL_VirtualListBox::WDL_VirtualListBox()
{
m_scrollbar_color = LICE_RGBA(0,128,255,255);
m_scrollbar_blendmode=LICE_BLIT_MODE_COPY;
m_scrollbar_alpha = 1.0f;
m_scrollbar_size=4;
m_scrollbar_border=1;
m_scrollbar_expanded = false;
m_want_wordwise_cols = false;
m_cap_startitem=-1;
m_cap_state=0;
m_cap_startpos.x = m_cap_startpos.y = 0;
m_margin_l=m_margin_r=0;
m_GetItemInfo=0;
m_CustomDraw=0;
m_GetItemHeight=NULL;
m_GetItemInfo_ctx=0;
m_viewoffs=0;
m_align=-1;
m_rh=14;
m_maxcolwidth=m_mincolwidth=0;
m_colgap=0;
m_lsadj=-1000;
m_font=0;
m_clickmsg=0;
m_dropmsg=0;
m_dragmsg=0;
m_grayed=false;
}
WDL_VirtualListBox::~WDL_VirtualListBox()
{
}
static bool items_fit(int test_h, const int *heights, int heights_sz, int h, int max_cols, int rh_base)
{
int y=test_h, cols=1;
for (int item = 0; item < heights_sz; item ++)
{
int rh = heights[item] & WDL_VirtualListBox::ITEMH_MASK;
if (y > 0 && y + rh > h)
{
if (max_cols == 1 && !(heights[item]&WDL_VirtualListBox::ITEMH_FLAG_NOSQUISH) &&
y + rh_base <= h) return item == heights_sz-1; // allow partial of larger item in single column mode
if (++cols > max_cols) return false;
y=0;
}
y += rh;
}
return true;
}
void WDL_VirtualListBox::CalcLayout(int num_items, layout_info *layout)
{
const int w = m_position.right - m_position.left, h = m_position.bottom - m_position.top;
int max_cols = 1, min_cols = 1, max_cols2 = 1;
if (m_mincolwidth>0)
{
max_cols = w / m_mincolwidth;
max_cols2 = (w - m_scrollbar_size - m_colgap) / m_mincolwidth; // maximum column count when a scrollbar is visible
if (max_cols < 1) max_cols = 1;
if (max_cols2 < 1) max_cols2 = 1;
}
if (m_maxcolwidth>0)
{
min_cols = w / m_maxcolwidth;
if (min_cols < 1) min_cols = 1;
}
if (max_cols < min_cols) max_cols = min_cols;
if (max_cols2 < min_cols) max_cols2 = min_cols;
static WDL_TypedBuf<int> s_heights;
again:
s_heights.Resize(0,false);
int maxvis = 1;
if (!m_GetItemHeight)
{
const int rh = GetRowHeight();
if (WDL_NORMALLY(rh>0)) maxvis = wdl_max(h/rh,1) * max_cols;
}
int startitem = wdl_min(m_viewoffs,num_items-maxvis), cols = 1, y = 0;
if (startitem < 0) startitem = 0;
const int rh_base = GetRowHeight();
int item;
for (item = startitem; item < num_items; item ++)
{
int flag=0;
int rh = GetItemHeight(item, &flag);
if (y > 0 && y + rh > h)
{
if (cols == max_cols && !(flag&ITEMH_FLAG_NOSQUISH) && y + rh_base <= h) s_heights.Add(rh | flag); // allow partial of larger item in single column mode
if (cols >= max_cols) break;
cols++;
y=0;
}
s_heights.Add(rh | flag);
y += rh;
}
if (item >= num_items)
{
int use_col = wdl_max(min_cols,cols);
if (m_GetItemHeight == NULL && m_want_wordwise_cols && use_col>1 && h >= rh_base*2)
{
int viscnt = use_col * (h/rh_base);
if (startitem + viscnt > num_items + use_col-1)
{
startitem = num_items-viscnt + use_col-1;
if (startitem < 0) startitem=0;
}
}
else
{
int use_h = h;
if (use_col > 1 && num_items >= use_col)
use_h -= m_scrollbar_size;
while (startitem > 0)
{
int flag=0;
int rh = GetItemHeight(startitem-1, &flag);
if (!items_fit(rh,s_heights.Get(),s_heights.GetSize(),use_h,max_cols,rh_base)) break;
s_heights.Insert(rh | flag,0);
startitem--;
}
}
}
const bool has_scroll = item < num_items || startitem > 0;
if (has_scroll && cols > wdl_max(min_cols,1) && max_cols > max_cols2)
{
max_cols = max_cols2;
goto again;
}
if (cols < min_cols) cols = min_cols;
layout->startpos = startitem;
layout->columns = cols;
layout->heights = &s_heights;
int scroll_mode = 0;
if (has_scroll)
{
if (cols > 1 && (m_GetItemHeight!=NULL || !m_want_wordwise_cols || h < rh_base*2))
scroll_mode=2;
else
scroll_mode=1;
}
layout->vscrollbar_w = scroll_mode == 1 ? m_scrollbar_size : 0;
layout->hscrollbar_h = scroll_mode == 2 ? m_scrollbar_size : 0;
layout->item_area_w = w - layout->vscrollbar_w;
layout->item_area_h = wdl_max(h - layout->hscrollbar_h,rh_base);
if (AreItemsWordWise(*layout))
{
int adj = layout->startpos%layout->columns;
for (int x = 0; x <adj; x++)
s_heights.Insert(rh_base,0);
layout->startpos -= adj;
}
else if (scroll_mode == 2 && m_GetItemHeight == NULL && rh_base > 0)
{
const int rowcnt = layout->item_area_h / rh_base;
if (rowcnt > 1)
{
int adj = rowcnt - (layout->startpos%rowcnt);
if (adj < rowcnt)
{
if (adj > s_heights.GetSize()) adj = s_heights.GetSize();
layout->startpos += adj;
for (int x = 0; x < adj; x++) s_heights.Delete(0);
}
}
}
}
static void maxSizePreservingAspect(int sw, int sh, int dw, int dh, int *outw, int *outh)
{
*outw=dw;
*outh=dh;
if (sw < 1 || sh < 1) return;
int xwid = (sw * dh) / sh; // calculate width required if we make it dh pixels tall
if (xwid > dw)
{
// too wide, make maximum width and reduce height
*outh = (dw * sh) / sw;
}
else
{
// too narrow, use full height and reduce width
*outw = (dh * sw) / sh;
}
}
static void DrawBkImage(LICE_IBitmap *drawbm, WDL_VirtualWnd_BGCfg *bkbm, int drawx, int drawy, int draww, int drawh,
RECT *cliprect, int drawsrcx, int drawsrcw, int bkbmstate, float alpha, int whichpass=0)
{
bool haspink=bkbm->bgimage_lt[0]||bkbm->bgimage_lt[1]||bkbm->bgimage_rb[0] || bkbm->bgimage_rb[1];
if (whichpass==1)
{
int sw = (bkbm->bgimage->getWidth() - (haspink? 2 :0))/2;
int sh = (bkbm->bgimage->getHeight() - (haspink? 2 :0))/3;
int usew,useh;
// scale drawing coords by image dimensions
maxSizePreservingAspect(sw,sh,draww,drawh,&usew,&useh);
if (usew == sw-1 || usew == sw+1) usew=sw;
if (useh == sh-1 || useh == sh+1) useh=sh;
drawx += (draww-usew)/2;
drawy += (drawh-useh)/2;
draww = usew;
drawh = useh;
}
int hh=bkbm->bgimage->getHeight()/3;
if (haspink)
{
WDL_VirtualWnd_BGCfg tmp = *bkbm;
if ((tmp.bgimage_noalphaflags&0xffff)!=0xffff) tmp.bgimage_noalphaflags=0; // force alpha channel if any alpha
if (drawsrcx>0) { drawsrcx--; drawsrcw++; }
LICE_SubBitmap bm(tmp.bgimage,drawsrcx,bkbmstate*hh,drawsrcw+1,hh+2);
tmp.bgimage = &bm;
WDL_VirtualWnd_ScaledBlitBG(drawbm,&tmp,
drawx,drawy,draww,drawh,
cliprect->left,cliprect->top,cliprect->right-cliprect->left,cliprect->bottom-cliprect->top,
alpha,LICE_BLIT_USE_ALPHA|LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR);
}
else
{
LICE_ScaledBlit(drawbm,bkbm->bgimage,
drawx,drawy,draww,drawh,
(float)drawsrcx,bkbmstate*(float)hh,
(float)drawsrcw,(float)hh,alpha,LICE_BLIT_USE_ALPHA|LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR);
}
}
int WDL_VirtualListBox::ScrollbarGetInfo(int *start, int *size, int num_items, const layout_info &layout)
{
if (!layout.vscrollbar_w)
{
if (layout.hscrollbar_h)
{
const int total = wdl_max(num_items,1);
const int cols = wdl_max(layout.columns,1);
int vis = (layout.heights->GetSize() + cols - 1);
vis -= (vis % cols);
if (vis < cols) vis=cols;
int startp = (layout.startpos * layout.item_area_w) / total;
int endp = ((layout.startpos + vis) * layout.item_area_w) / total;
*start = startp;
*size = endp-startp;
if (*size<2) { if (*start>0) (*start)--; *size=2; }
if (*start + *size > layout.item_area_w) *start = layout.item_area_w - *size;
if (*start < 0) *start=0;
return 2;
}
return 0;
}
const int num_cols = wdl_max(layout.columns,1);
const int total_rows = wdl_max((num_items+num_cols-1) / num_cols,1);
const int rh_base = wdl_max(GetRowHeight(),1);
const int vis_rows =
layout.columns > 1 ? wdl_clamp(layout.item_area_h / rh_base,1,total_rows) :
wdl_clamp(layout.heights->GetSize(),1,total_rows);
const int srow = layout.startpos/num_cols;
int startp = (srow * layout.item_area_h) / total_rows;
int endp = ((srow + vis_rows) * layout.item_area_h) / total_rows;
*start = startp;
*size = endp-startp;
if (*size<2) { if (*start>0) (*start)--; *size=2; }
if (*start + *size > layout.item_area_h) *start = layout.item_area_h - *size;
if (*start < 0) *start=0;
return 1;
}
void WDL_VirtualListBox::OnPaint(LICE_IBitmap *drawbm, int origin_x, int origin_y, RECT *cliprect, int rscale)
{
RECT r;
WDL_VWnd::GetPositionPaintExtent(&r,rscale);
r.left+=origin_x;
r.right+=origin_x;
r.top+=origin_y;
r.bottom+=origin_y;
WDL_VirtualWnd_BGCfg *mainbk=0;
int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,&mainbk) : 0;
LICE_pixel bgc=GSC(COLOR_BTNFACE);
bgc=LICE_RGBA_FROMNATIVE(bgc,255);
layout_info layout;
CalcLayout(num_items,&layout);
m_viewoffs = layout.startpos; // latch to new startpos
const int num_cols = layout.columns;
const bool do_wordwise = AreItemsWordWise(layout);
const int usedw = layout.item_area_w * rscale / WDL_VWND_SCALEBASE;
const int vscrollsize = layout.vscrollbar_w * rscale / WDL_VWND_SCALEBASE;
const int endpos=layout.item_area_h * rscale / WDL_VWND_SCALEBASE;
const int startpos = m_viewoffs;
const int rh_base = GetRowHeight();
if (r.right > r.left + usedw+vscrollsize) r.right=r.left+usedw+vscrollsize;
if (mainbk && mainbk->bgimage)
{
if (mainbk->bgimage->getWidth()>1 && mainbk->bgimage->getHeight()>1)
{
WDL_VirtualWnd_ScaledBlitBG(drawbm,mainbk,
r.left,r.top,r.right-r.left,r.bottom-r.top,
cliprect->left,cliprect->top,cliprect->right-cliprect->left,cliprect->bottom-cliprect->top,
1.0,LICE_BLIT_USE_ALPHA|LICE_BLIT_MODE_COPY|LICE_BLIT_FILTER_BILINEAR);
}
}
LICE_pixel pencol = GSC(COLOR_3DSHADOW);
LICE_pixel pencol2 = GSC(COLOR_3DHILIGHT);
pencol=LICE_RGBA_FROMNATIVE(pencol,255);
pencol2=LICE_RGBA_FROMNATIVE(pencol2,255);
LICE_pixel tcol=GSC(COLOR_BTNTEXT);
if (m_font)
{
m_font->SetBkMode(TRANSPARENT);
if (m_lsadj != -1000)
m_font->SetLineSpacingAdjust(m_lsadj);
}
float alpha = (m_grayed ? 0.25f : 1.0f);
int itempos=startpos;
for (int colpos = 0; colpos < (do_wordwise ? 1 : num_cols); colpos ++)
{
int col_x = r.left + ((usedw+m_colgap)*colpos) / num_cols;
int col_w = r.left + ((usedw+m_colgap)*(colpos+1)) / num_cols - col_x - m_colgap;
int y=r.top, ly = y;
int cstate=0;
for (;;)
{
const int idx = itempos-startpos;
int rh,flag;
if (idx >= 0 && idx < layout.heights->GetSize())
rh = layout.GetHeight(idx,&flag);
else
rh = GetItemHeight(itempos,&flag);
rh = rh * rscale / WDL_VWND_SCALEBASE;
if (!do_wordwise)
{
ly=y;
y += rh;
if (y > r.top+endpos)
{
if (ly != r.top)
{
if (y+ ((flag&ITEMH_FLAG_NOSQUISH) ? 0 : -rh + rh_base) > r.top+endpos) break;
if (colpos < num_cols-1) break;
}
y = r.top+endpos; // size expanded-sized item to fit
}
}
else
{
col_x = r.left + ((usedw+m_colgap)*cstate) / num_cols;
col_w = r.left + ((usedw+m_colgap)*(cstate+1)) / num_cols - col_x - m_colgap;
if (!cstate)
{
ly = y;
y += rh;
if (y > r.top+endpos) break;
}
if (++cstate == num_cols) cstate=0;
}
WDL_VirtualWnd_BGCfg *bkbm=0;
if (m_GetItemInfo && ly >= r.top)
{
char buf[64];
buf[0]=0;
int color=tcol;
if (m_GetItemInfo(this,itempos,buf,sizeof(buf),&color,&bkbm))
{
color=LICE_RGBA_FROMNATIVE(color,0);
RECT thisr;
thisr.left = col_x;
thisr.right = col_x + col_w;
thisr.top = ly+1;
thisr.bottom = y-1;
int rev=0;
int bkbmstate=0;
if (m_cap_state==1 && m_cap_startitem==itempos)
{
if (bkbm) bkbmstate=1;
else color = ((color>>1)&0x7f7f7f7f)+LICE_RGBA(0x7f,0x7f,0x7f,0);
}
if (m_cap_state>=0x1000 && m_cap_startitem==itempos)
{
if (bkbm) bkbmstate=2;
else
{
rev=1;
LICE_FillRect(drawbm,thisr.left,thisr.top,thisr.right-thisr.left,thisr.bottom-thisr.top, color,alpha,LICE_BLIT_MODE_COPY);
}
}
if (bkbm && bkbm->bgimage) //draw image!
{
DrawBkImage(drawbm,bkbm,
thisr.left,thisr.top-1,thisr.right-thisr.left,thisr.bottom-thisr.top+2,
cliprect,
0,bkbm->bgimage->getWidth(),bkbmstate,alpha);
}
if (m_CustomDraw)
{
m_CustomDraw(this,itempos,&thisr,drawbm,rscale);
}
if (buf[0])
{
thisr.left+=m_margin_l;
thisr.right-=m_margin_r;
if (m_font)
{
m_font->SetTextColor(rev?bgc:color);
m_font->SetCombineMode(LICE_BLIT_MODE_COPY, alpha); // maybe gray text only if !bkbm->bgimage
RECT dr=thisr;
#ifdef __APPLE__
OffsetRect(&dr,0,2);
#endif
const bool has_nl = strchr(buf,'\n') != NULL;
RECT r2 = { 0, };
if (has_nl || m_align == 0)
m_font->DrawText(drawbm,buf,-1,&r2,DT_CALCRECT|DT_NOPREFIX);
int f = (m_align > 0 ? DT_RIGHT : ((m_align == 0 && r2.right <= thisr.right-thisr.left) ? DT_CENTER : DT_LEFT));
if (has_nl)
{
// can't use DT_VCENTER
OffsetRect(&dr,0,((dr.bottom-dr.top) - (r2.bottom-r2.top))/2);
}
else
{
f |= DT_SINGLELINE | DT_VCENTER;
}
m_font->DrawText(drawbm,buf,-1,&dr,f | DT_NOPREFIX);
}
}
}
}
itempos++;
if (!bkbm)
{
LICE_Line(drawbm,col_x,y,col_x+col_w,y,pencol2,1.0f,LICE_BLIT_MODE_COPY,false);
}
}
}
if (num_cols>0)
{
int offs, height;
int stype = ScrollbarGetInfo(&offs,&height,num_items,layout);
if (stype)
{
if (rscale != 256)
{
offs = offs*rscale / WDL_VWND_SCALEBASE;
height = height*rscale / WDL_VWND_SCALEBASE;
}
int w = stype == 1 ? vscrollsize : (layout.hscrollbar_h * rscale / WDL_VWND_SCALEBASE);
int drawposx = stype == 1 ? (r.left + usedw) : (r.top + endpos);
if (m_scrollbar_expanded)
{
drawposx -= EXPAND_SCROLLBAR_SIZE(w);
w += EXPAND_SCROLLBAR_SIZE(w);
}
else
{
int border = m_scrollbar_border * rscale / WDL_VWND_SCALEBASE;
if (border>0 && border < w)
{
w-=border;
drawposx+=border;
}
}
const LICE_pixel col = m_scrollbar_color;
const int mode = m_scrollbar_blendmode;
const float alpha = m_scrollbar_alpha;
if (stype == 1)
{
LICE_FillRect(drawbm,drawposx,r.top,w,endpos, col,alpha*.5f,mode);
LICE_FillRect(drawbm,drawposx,r.top + offs,w,height, col,alpha,mode);
}
else
{
if (drawposx+w > r.bottom) w = r.bottom - drawposx;
LICE_FillRect(drawbm,r.left,drawposx,usedw,w, col,alpha*.5f,mode);
LICE_FillRect(drawbm,r.left+offs,drawposx,height,w, col,alpha,mode);
}
}
}
if (!mainbk)
{
LICE_Line(drawbm,r.left,r.bottom-1,r.left,r.top,pencol,1.0f,0,false);
LICE_Line(drawbm,r.left,r.top,r.right-1,r.top,pencol,1.0f,0,false);
LICE_Line(drawbm,r.right-1,r.top,r.right-1,r.bottom-1,pencol2,1.0f,0,false);
LICE_Line(drawbm,r.right-1,r.bottom-1,r.left,r.bottom-1,pencol2,1.0f,0,false);
}
}
void WDL_VirtualListBox::DoScroll(int dir, const layout_info *layout)
{
if (dir < 0 && layout->columns>1)
{
int y=0;
if (AreItemsWordWise(*layout))
{
int np = wdl_max(0,m_viewoffs-layout->columns);
if (m_viewoffs != np) { m_viewoffs = np; y=1; }
}
else while (m_viewoffs>0)
{
y += GetItemHeight(--m_viewoffs);
if (y >= layout->item_area_h) break;
}
if (y) RequestRedraw(NULL);
}
else if (dir > 0 && layout->columns>1)
{
int y=0;
if (AreItemsWordWise(*layout))
{
m_viewoffs+=layout->columns;
y++;
}
else for (int i = 0; i < layout->heights->GetSize(); i ++)
{
m_viewoffs++;
y += layout->GetHeight(i);
if (y >= layout->item_area_h) break;
}
if (y)
RequestRedraw(NULL);
}
else if (dir > 0)
{
m_viewoffs++; // let painting sort out total visibility (we could easily calculate but meh)
RequestRedraw(NULL);
}
else if (dir < 0)
{
if (m_viewoffs>0)
{
m_viewoffs--;
RequestRedraw(NULL);
}
}
}
int WDL_VirtualListBox::OnMouseDown(int xpos, int ypos)
{
if (m_grayed) return 0;
m_cap_startpos.x = xpos;
m_cap_startpos.y = ypos;
if (m__iaccess) m__iaccess->OnFocused();
const int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
if (ScrollbarHit(xpos,ypos,layout))
{
int offs,sz;
switch (ScrollbarGetInfo(&offs,&sz,num_items,layout))
{
case 1:
if (ypos < offs || ypos > offs+sz)
{
m_viewoffs = (num_items * (ypos - sz/2)) / wdl_max(1,layout.item_area_h);
if (m_viewoffs<0) m_viewoffs=0;
if (m_viewoffs>num_items) m_viewoffs=num_items;
m_cap_startpos.y = sz/2;
}
else
m_cap_startpos.y = ypos - offs;
m_scrollbar_expanded = true;
m_cap_state=2;
RequestRedraw(NULL);
return 1;
case 2:
if (xpos < offs || xpos > offs+sz)
{
m_viewoffs = (num_items * (xpos - sz/2)) / wdl_max(1,layout.item_area_w);
if (m_viewoffs<0) m_viewoffs=0;
if (m_viewoffs>num_items) m_viewoffs=num_items;
m_cap_startpos.x = sz/2;
}
else
m_cap_startpos.x = xpos - offs;
m_scrollbar_expanded = true;
m_cap_state=3;
RequestRedraw(NULL);
return 1;
}
}
int idx = IndexFromPtInt(xpos,ypos,layout);
if (idx < 0)
return 0;
m_cap_state=0x1000;
m_cap_startitem=idx;
RequestRedraw(NULL);
return 1;
}
bool WDL_VirtualListBox::OnMouseDblClick(int xpos, int ypos)
{
if (m_grayed) return false;
const int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
int idx = IndexFromPtInt(xpos,ypos,layout);
if (idx<0)
{
if (idx == -2) return false;
idx = num_items;
}
RequestRedraw(NULL);
if (m_clickmsg) SendCommand(m_clickmsg,(INT_PTR)this,idx,this);
return false;
}
bool WDL_VirtualListBox::OnMouseWheel(int xpos, int ypos, int amt)
{
if (m_grayed) return false;
const int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
if (xpos >= layout.item_area_w + layout.vscrollbar_w) return false;
DoScroll(-amt,&layout);
return true;
}
void WDL_VirtualListBox::OnMouseMove(int xpos, int ypos)
{
if (m_grayed) return;
if (m_cap_state>=0x1000)
{
m_cap_state++;
if (m_cap_state < 0x1008)
{
int dx = (xpos - m_cap_startpos.x), dy=(ypos-m_cap_startpos.y);
if (dx*dx + dy*dy > 36)
m_cap_state=0x1008;
}
if (m_cap_state>=0x1008)
{
if (m_dragmsg)
{
SendCommand(m_dragmsg,(INT_PTR)this,m_cap_startitem,this);
}
}
}
else if (m_cap_state >= 0 && m_cap_state <= 3)
{
const int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
if (m_cap_state==0)
{
int a=IndexFromPtInt(xpos,ypos,layout);
if (a>=0)
{
m_cap_startitem=a;
m_cap_state=1;
RequestRedraw(NULL);
}
}
else if (m_cap_state==1)
{
int a=IndexFromPtInt(xpos,ypos,layout);
if (a>=0 && a != m_cap_startitem)
{
m_cap_startitem=a;
m_cap_state=1;
RequestRedraw(NULL);
}
else if (a<0)
{
m_cap_state=0;
RequestRedraw(NULL);
}
}
else if (m_cap_state==2)
{
int vwoffs = (num_items * (ypos - m_cap_startpos.y)) / wdl_max(1,layout.item_area_h);
if (vwoffs<0) vwoffs=0;
if (vwoffs>num_items) vwoffs=num_items;
if (vwoffs != m_viewoffs)
{
m_viewoffs = vwoffs;
RequestRedraw(NULL);
}
}
else if (m_cap_state==3)
{
int vwoffs = (num_items * (xpos - m_cap_startpos.x)) / wdl_max(1,layout.item_area_w);
if (vwoffs<0) vwoffs=0;
if (vwoffs>num_items) vwoffs=num_items;
if (vwoffs != m_viewoffs)
{
m_viewoffs = vwoffs;
RequestRedraw(NULL);
}
}
bool exp = (m_cap_state>=2 || ScrollbarHit(xpos,ypos,layout));
if (exp != m_scrollbar_expanded)
{
m_scrollbar_expanded = exp;
RequestRedraw(NULL);
}
}
}
bool WDL_VirtualListBox::ScrollbarHit(int xpos, int ypos, const layout_info &layout)
{
if (layout.vscrollbar_w>0)
{
int extra = m_scrollbar_expanded ? EXPAND_SCROLLBAR_SIZE(layout.vscrollbar_w) : 0;
return ypos>=0 && ypos < layout.item_area_h &&
xpos >= layout.item_area_w - extra && xpos < layout.item_area_w + layout.vscrollbar_w;
}
else if (layout.hscrollbar_h>0)
{
int extra = m_scrollbar_expanded ? EXPAND_SCROLLBAR_SIZE(layout.hscrollbar_h) : 0;
return xpos>=0 && xpos < layout.item_area_w &&
ypos >= layout.item_area_h - extra && ypos < layout.item_area_h + layout.hscrollbar_h;
}
return false;
}
void WDL_VirtualListBox::OnMouseUp(int xpos, int ypos)
{
if (m_grayed) return;
const int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
int cmd=0;
INT_PTR p1, p2;
int hit=IndexFromPtInt(xpos,ypos,layout);
if (m_cap_state>=0x1000 && m_cap_state<0x1008 && hit==m_cap_startitem)
{
if (m_clickmsg)
{
cmd=m_clickmsg;
p1=(INT_PTR)this;
p2=hit;
}
}
else if (m_cap_state>=0x1008)
{
// send a message saying drag & drop occurred
if (m_dropmsg)
{
cmd=m_dropmsg;
p1=(INT_PTR)this;
p2=m_cap_startitem;
}
}
else if (m_cap_state == 2 || m_cap_state == 3)
{
m_scrollbar_expanded = ScrollbarHit(xpos,ypos,layout);
}
m_cap_state=0;
RequestRedraw(NULL);
if (cmd) SendCommand(cmd,p1,p2,this);
}
bool WDL_VirtualListBox::GetItemRect(int item, RECT *r)
{
const int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
item -= layout.startpos;
if (item < 0) { if (r) memset(r,0,sizeof(RECT)); return false; }
if (r)
{
const bool do_wordwise = AreItemsWordWise(layout);
const int rh_base = GetRowHeight();
int col = 0,y=0;
for (int x=0;x<item;x++)
{
if (do_wordwise)
{
if (++col == layout.columns)
{
y += rh_base;
col = 0;
}
}
else
{
int flag = 0;
const int rh = x < layout.heights->GetSize() ? layout.GetHeight(x,&flag) : rh_base;
if (y > 0 && y + rh > layout.item_area_h && (col < layout.columns-1 || (flag&ITEMH_FLAG_NOSQUISH) || y+rh_base > layout.item_area_h)) { col++; y = 0; }
y += rh;
}
}
int flag = 0;
const int rh = item < layout.heights->GetSize() ? layout.GetHeight(item,&flag) : rh_base;
if (!do_wordwise)
{
if (y > 0 && y + rh > layout.item_area_h && (col < layout.columns-1 || (flag&ITEMH_FLAG_NOSQUISH) || y+rh_base > layout.item_area_h)) { col++; y = 0; }
}
if (col >= layout.columns) { if (r) memset(r,0,sizeof(RECT)); return false; }
r->top = y;
r->bottom = y+rh;
if (r->bottom > layout.item_area_h) r->bottom = layout.item_area_h;
r->left = (col * layout.item_area_w) / layout.columns;
r->right = ((col+1) * layout.item_area_w) / layout.columns;
if (col == layout.columns-1 && layout.vscrollbar_w && m_scrollbar_expanded)
{
r->right -= EXPAND_SCROLLBAR_SIZE(layout.vscrollbar_w); // exclude the expanded-scrollbar area from the rect
}
}
return true;
}
int WDL_VirtualListBox::IndexFromPt(int x, int y)
{
const int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
return IndexFromPtInt(x,y,layout);
}
int WDL_VirtualListBox::IndexFromPtInt(int x, int y, const layout_info &layout)
{
if (x >= layout.item_area_w - (m_scrollbar_expanded ? EXPAND_SCROLLBAR_SIZE(layout.vscrollbar_w) : 0)) return -2;
if (y < 0 || y >= layout.item_area_h || x < 0)
{
return -1;
}
// step through visible items
const int usewid=layout.item_area_w;
int xpos = 0;
int col = 0;
const int do_wordwise = AreItemsWordWise(layout);
const int rh_base = GetRowHeight();
int idx = 0;
if (do_wordwise)
{
int ypos = 0;
for (;;)
{
const int nx = (++col * usewid) / layout.columns;
int flag = 0;
const int rh = idx < layout.heights->GetSize() ? layout.GetHeight(idx,&flag) : rh_base;
if (x < nx && y >= ypos && y < ypos+rh) return layout.startpos + idx;
if (col == layout.columns)
{
col = 0;
ypos += rh;
}
idx++;
}
}
else for (;;)
{
if (x < xpos) return -1;
int ypos = 0;
const int nx = ((col+1) * usewid) / layout.columns;
for (;;)
{
int flag = 0;
const int rh = idx < layout.heights->GetSize() ? layout.GetHeight(idx,&flag) : rh_base;
if (ypos > 0 && ypos + rh > layout.item_area_h && (col < layout.columns-1 || (flag & ITEMH_FLAG_NOSQUISH) || ypos+rh_base > layout.item_area_h))
{
// item doesn't fit in this column
if (x < nx) return -1; // stop looking we didn't hit anything
break;
}
if (x < nx && y >= ypos && y < ypos+rh) return layout.startpos + idx;
ypos += rh;
idx++;
}
if (++col >= layout.columns) return -1;
if (x < nx && y >= ypos) return -1; // empty space at bottom of column
xpos = nx;
}
}
void WDL_VirtualListBox::SetViewOffset(int offs)
{
m_viewoffs = wdl_max(offs,0);
RequestRedraw(0);
}
int WDL_VirtualListBox::GetViewOffset()
{
return m_viewoffs;
}
int WDL_VirtualListBox::GetItemHeight(int idx, int *flag)
{
const int a = m_GetItemHeight ? m_GetItemHeight(this,idx) : -1;
if (flag) *flag = a<0 ? 0 : (a&~ITEMH_MASK);
return a>=0 ? (a&ITEMH_MASK) : GetRowHeight();
}
RECT *WDL_VirtualListBox::GetScrollButtonRect(bool isDown)
{
// used for accessibility!
static RECT ret;
int num_items = m_GetItemInfo ? m_GetItemInfo(this,-1,NULL,0,NULL,NULL) : 0;
layout_info layout;
CalcLayout(num_items,&layout);
int start,sz;
int sb = ScrollbarGetInfo(&start,&sz,num_items,layout);
if (sb == 1 && layout.vscrollbar_w>0)
{
if (isDown && start+sz >= layout.item_area_h) return NULL;
if (!isDown && start <= 0) return NULL;
ret.left = layout.item_area_w;
ret.right = ret.left + layout.vscrollbar_w;
ret.top = isDown ? start+sz : start - layout.vscrollbar_w;
ret.bottom = ret.top + layout.vscrollbar_w;
return &ret;
}
if (sb ==2 && layout.hscrollbar_h>0)
{
if (isDown && start+sz >= layout.item_area_w) return NULL;
if (!isDown && start <= 0) return NULL;
ret.top = layout.item_area_h;
ret.bottom = ret.top + layout.hscrollbar_h;
ret.left = isDown ? start+sz : start - layout.hscrollbar_h;
ret.right = ret.left + layout.hscrollbar_h;
return &ret;
}
return NULL;
}