*/}}
Browse Source

Thumbnailer support for file browser

YimingWu 3 weeks ago
parent
commit
3798abd026
10 changed files with 336 additions and 53 deletions
  1. 3 0
      la_interface.h
  2. 1 1
      la_kernel.c
  3. 3 0
      la_tns.h
  4. 53 36
      la_tns_kernel.c
  5. 193 0
      la_util.c
  6. 18 0
      la_util.h
  7. 35 9
      resources/la_operators.c
  8. 11 1
      resources/la_templates.c
  9. 2 1
      resources/la_translations.c
  10. 17 5
      resources/la_widgets.c

+ 3 - 0
la_interface.h

@@ -2177,6 +2177,8 @@ STRUCTURE(laFileBrowser){
     char Path[2048];
     char FileName[512];
     char TempStr[512];
+    char MD5[128];
+    tnsImage* Thumbnail;
     laDiskItem *RootDisk;
     laFileItem *Active;
     laListHandle FileList;
@@ -2190,6 +2192,7 @@ STRUCTURE(laFileBrowser){
     int UseType;
     int FilterType;
     int ShowBackups;
+    int ShowThumbnail;
     int SortBy;
     laStringSplitor* ss_filter_extensions;
 };

+ 1 - 1
la_kernel.c

@@ -4244,7 +4244,7 @@ laUiItem *laShowItemFull(laUiList *uil, laColumn *c, laPropPack *Base, const cha
 }
 laUiItem *laShowImage(laUiList *uil, laColumn *c, tnsImage* Image, int Height){
     laUiItem *ui = memAcquireSimple(sizeof(laUiItem));
-    ui->C = c; ui->Extra=Image;
+    ui->C = c; memAssignRef(ui,&ui->Extra,Image);
     ui->Type=_LA_UI_IMAGE; ui->Type->Init(ui);
     ui->SymbolID=Height;
     lstAppendItem(&uil->UiItems, ui);

+ 3 - 0
la_tns.h

@@ -1365,9 +1365,12 @@ void tnsUseHalftone(real Use);
 
 void tnsUniformUseOffset(tnsShader* s, int use);
 
+void tns_ImageToTexture(tnsImage* im);
 tnsImage* tnsNewImage(void* MemPNG);
 void tnsUseImage(tnsImage* im);
 void tnsStopUsingImage(tnsImage* im);
+void tnsRefreshImage(tnsImage* im,void* data);
+void tnsRemoveImage(tnsImage* im);
 
 void tnsSetRayShaderUniformTextures(tnsOffscreen* doff);
 int tnsInit2DTexture(tnsTexture *t, GLint glInternalFormat, int w, int h, int Multisample);

+ 53 - 36
la_tns_kernel.c

@@ -1991,6 +1991,7 @@ int tnsTextureMemorySize(tnsTexture *t, int Mem){
 
 tnsImage* tnsNewImage(void* MemPNG){
     tnsImage* im=memAcquire(sizeof(tnsImage));
+    lstAppendItem(&T->Images,im);
     im->MemPNG=MemPNG; return im;
 }
 STRUCTURE(tnsPNGRead){
@@ -2002,54 +2003,70 @@ static void _tns_png_read(png_struct *ps, png_byte *data, png_size_t length){
     memcpy(data,&PNGRead->data[PNGRead->NextData],length);
     PNGRead->NextData+=length;
 }
-void tnsUseImage(tnsImage* im){
+void tns_ImageToTexture(tnsImage* im){
+    if(!im->MemPNG) return;
     png_structp png_ptr=0;
     png_infop info_ptr=0;
-    if(im->UserCount==0){
-        tnsPNGRead PNGRead={0};
-
-        png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,0,0,0); if (!png_ptr) { goto cleanup_png_read; }
-        info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { goto cleanup_png_read; }
-        if (setjmp(png_jmpbuf(png_ptr))) { goto cleanup_png_read; }
-        PNGRead.data=im->MemPNG; png_set_read_fn(png_ptr, &PNGRead, _tns_png_read);
-        png_read_info(png_ptr, info_ptr);
-        png_set_swap(png_ptr);
-        if (png_get_interlace_type (png_ptr, info_ptr) != PNG_INTERLACE_NONE){ goto cleanup_png_read; }
-
-        png_byte ColorType = png_get_color_type(png_ptr, info_ptr);
-        png_byte BitDepth = png_get_bit_depth(png_ptr, info_ptr);
-        int HasAlpha = ColorType & PNG_COLOR_MASK_ALPHA;
-        if (ColorType == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); }
-        //if (ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8) { png_set_expand_gray_1_2_4_to_8(png_ptr); }
-        if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png_ptr); HasAlpha = 1; }
-        if (BitDepth>8) {png_set_strip_16(png_ptr);} if (BitDepth<8) { png_set_expand(png_ptr); }
-        if (!HasAlpha) { png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); }
-        if (ColorType == PNG_COLOR_TYPE_GRAY || ColorType == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); }
-        png_read_update_info(png_ptr, info_ptr);
-        if (png_get_bit_depth(png_ptr, info_ptr)!=8) { goto cleanup_png_read; }
-        if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_RGB_ALPHA){ goto cleanup_png_read; }
-        if (png_get_channels(png_ptr, info_ptr) != 4) { goto cleanup_png_read; }
-
-        int W = png_get_image_width(png_ptr, info_ptr);
-        int H = png_get_image_height(png_ptr, info_ptr);
-        
-        unsigned char* buf=calloc(W*4,H*sizeof(unsigned char));
+    tnsPNGRead PNGRead={0};
+    png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,0,0,0); if (!png_ptr) { goto cleanup_png_read; }
+    info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { goto cleanup_png_read; }
+    if (setjmp(png_jmpbuf(png_ptr))) { goto cleanup_png_read; }
+    PNGRead.data=im->MemPNG; png_set_read_fn(png_ptr, &PNGRead, _tns_png_read);
+    png_read_info(png_ptr, info_ptr);
+    png_set_swap(png_ptr);
+    if (png_get_interlace_type (png_ptr, info_ptr) != PNG_INTERLACE_NONE){ goto cleanup_png_read; }
+
+    png_byte ColorType = png_get_color_type(png_ptr, info_ptr);
+    png_byte BitDepth = png_get_bit_depth(png_ptr, info_ptr);
+    int HasAlpha = ColorType & PNG_COLOR_MASK_ALPHA;
+    if (ColorType == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); }
+    //if (ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8) { png_set_expand_gray_1_2_4_to_8(png_ptr); }
+    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png_ptr); HasAlpha = 1; }
+    if (BitDepth>8) {png_set_strip_16(png_ptr);} if (BitDepth<8) { png_set_expand(png_ptr); }
+    if (!HasAlpha) { png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); }
+    if (ColorType == PNG_COLOR_TYPE_GRAY || ColorType == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); }
+    png_read_update_info(png_ptr, info_ptr);
+    if (png_get_bit_depth(png_ptr, info_ptr)!=8) { goto cleanup_png_read; }
+    if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_RGB_ALPHA){ goto cleanup_png_read; }
+    if (png_get_channels(png_ptr, info_ptr) != 4) { goto cleanup_png_read; }
+
+    int W = png_get_image_width(png_ptr, info_ptr);
+    int H = png_get_image_height(png_ptr, info_ptr);
+    
+    unsigned char* buf=calloc(W*4,H*sizeof(unsigned char));
 
-        for(int i=0;i<H;i++){ png_read_row(png_ptr, &buf[((H-i-1)*W)*4], NULL); }
-        im->Texture=tnsCreate2DTexture(GL_RGBA8,W,H,0);
-        tnsBindTexture(im->Texture); glTexSubImage2D(GL_TEXTURE_2D,0,0,0,W,H,GL_RGBA,GL_UNSIGNED_BYTE,buf);
-        tnsUnbindTexture();
+    for(int i=0;i<H;i++){ png_read_row(png_ptr, &buf[((H-i-1)*W)*4], NULL); }
+    im->Texture=tnsCreate2DTexture(GL_RGBA8,W,H,0);
+    tnsBindTexture(im->Texture); glTexSubImage2D(GL_TEXTURE_2D,0,0,0,W,H,GL_RGBA,GL_UNSIGNED_BYTE,buf);
+    tnsUnbindTexture();
+
+    free(buf);
 
-        free(buf);
-    }
 cleanup_png_read:
     if(png_ptr && info_ptr) png_destroy_read_struct(&png_ptr,&info_ptr,0);
+}
+void tnsUseImage(tnsImage* im){
+    if(im->UserCount==0 || !im->Texture){
+        if(!im->MemPNG){
+            if(im->Texture){ tnsDeleteTexture(im->Texture); im->Texture=0; }
+        }else{
+            tns_ImageToTexture(im);
+        }
+    }
     im->UserCount++;
 }
+void tnsRefreshImage(tnsImage* im,void* data){
+    if(im->MemPNG){free(im->MemPNG);}
+    im->MemPNG=data; tnsDeleteTexture(im->Texture); im->Texture=0;
+}
 void tnsStopUsingImage(tnsImage* im){
     im->UserCount--;
     if(im->UserCount<=0){ im->UserCount=0; tnsDeleteTexture(im->Texture); im->Texture=0; }
 }
+void tnsRemoveImage(tnsImage* im){
+    while(im->UserCount) tnsStopUsingImage(im);
+    free(im->MemPNG); lstRemoveItem(&T->Images,im); memFree(im);
+}
 
 #endif //png
 

+ 193 - 0
la_util.c

@@ -151,6 +151,8 @@ int nutSameAddress(void *l, void *r){
     return (l == r);
 }
 
+//===================================================================[barray]
+
 #ifdef _MSC_VER
 #  include <intrin.h>
 #  define __builtin_popcountll __popcnt64
@@ -229,6 +231,197 @@ void barray_foreach_set(barray_t *barray, barray_callback_t callback, void *arg)
         }
     }    
 }
+
+//===================================================================[md5]
+
+#define A 0x67452301
+#define B 0xefcdab89
+#define C 0x98badcfe
+#define D 0x10325476
+
+static uint32_t S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
+                       5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20,
+                       4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
+                       6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
+static uint32_t K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+                       0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+                       0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+                       0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+                       0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+                       0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+                       0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+                       0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+                       0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+                       0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+                       0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
+                       0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+                       0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+                       0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+                       0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+                       0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
+static uint8_t PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+#define F(X, Y, Z) ((X & Y) | (~X & Z))
+#define G(X, Y, Z) ((X & Z) | (Y & ~Z))
+#define H(X, Y, Z) (X ^ Y ^ Z)
+#define I(X, Y, Z) (Y ^ (X | ~Z))
+
+uint32_t rotateLeft(uint32_t x, uint32_t n){
+    return (x << n) | (x >> (32 - n));
+}
+
+void md5Init(MD5Context *ctx){
+    ctx->size = (uint64_t)0;
+
+    ctx->buffer[0] = (uint32_t)A;
+    ctx->buffer[1] = (uint32_t)B;
+    ctx->buffer[2] = (uint32_t)C;
+    ctx->buffer[3] = (uint32_t)D;
+}
+void md5Update(MD5Context *ctx, uint8_t *input_buffer, size_t input_len){
+    uint32_t input[16];
+    unsigned int offset = ctx->size % 64;
+    ctx->size += (uint64_t)input_len;
+
+    // Copy each byte in input_buffer into the next space in our context input
+    for(unsigned int i = 0; i < input_len; ++i){
+        ctx->input[offset++] = (uint8_t)*(input_buffer + i);
+
+        // If we've filled our context input, copy it into our local array input
+        // then reset the offset to 0 and fill in a new buffer.
+        // Every time we fill out a chunk, we run it through the algorithm
+        // to enable some back and forth between cpu and i/o
+        if(offset % 64 == 0){
+            for(unsigned int j = 0; j < 16; ++j){
+                // Convert to little-endian
+                // The local variable `input` our 512-bit chunk separated into 32-bit words
+                // we can use in calculations
+                input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
+                           (uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
+                           (uint32_t)(ctx->input[(j * 4) + 1]) <<  8 |
+                           (uint32_t)(ctx->input[(j * 4)]);
+            }
+            md5Step(ctx->buffer, input);
+            offset = 0;
+        }
+    }
+}
+void md5Finalize(MD5Context *ctx){
+    uint32_t input[16];
+    unsigned int offset = ctx->size % 64;
+    unsigned int padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset;
+
+    // Fill in the padding and undo the changes to size that resulted from the update
+    md5Update(ctx, PADDING, padding_length);
+    ctx->size -= (uint64_t)padding_length;
+
+    // Do a final update (internal to this function)
+    // Last two 32-bit words are the two halves of the size (converted from bytes to bits)
+    for(unsigned int j = 0; j < 14; ++j){
+        input[j] = (uint32_t)(ctx->input[(j * 4) + 3]) << 24 |
+                   (uint32_t)(ctx->input[(j * 4) + 2]) << 16 |
+                   (uint32_t)(ctx->input[(j * 4) + 1]) <<  8 |
+                   (uint32_t)(ctx->input[(j * 4)]);
+    }
+    input[14] = (uint32_t)(ctx->size * 8);
+    input[15] = (uint32_t)((ctx->size * 8) >> 32);
+
+    md5Step(ctx->buffer, input);
+
+    // Move the result into digest (convert from little-endian)
+    for(unsigned int i = 0; i < 4; ++i){
+        ctx->digest[(i * 4) + 0] = (uint8_t)((ctx->buffer[i] & 0x000000FF));
+        ctx->digest[(i * 4) + 1] = (uint8_t)((ctx->buffer[i] & 0x0000FF00) >>  8);
+        ctx->digest[(i * 4) + 2] = (uint8_t)((ctx->buffer[i] & 0x00FF0000) >> 16);
+        ctx->digest[(i * 4) + 3] = (uint8_t)((ctx->buffer[i] & 0xFF000000) >> 24);
+    }
+}
+void md5Step(uint32_t *buffer, uint32_t *input){
+    uint32_t AA = buffer[0];
+    uint32_t BB = buffer[1];
+    uint32_t CC = buffer[2];
+    uint32_t DD = buffer[3];
+
+    uint32_t E;
+
+    unsigned int j;
+
+    for(unsigned int i = 0; i < 64; ++i){
+        switch(i / 16){
+            case 0:
+                E = F(BB, CC, DD);
+                j = i;
+                break;
+            case 1:
+                E = G(BB, CC, DD);
+                j = ((i * 5) + 1) % 16;
+                break;
+            case 2:
+                E = H(BB, CC, DD);
+                j = ((i * 3) + 5) % 16;
+                break;
+            default:
+                E = I(BB, CC, DD);
+                j = (i * 7) % 16;
+                break;
+        }
+
+        uint32_t temp = DD;
+        DD = CC;
+        CC = BB;
+        BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]);
+        AA = temp;
+    }
+
+    buffer[0] += AA;
+    buffer[1] += BB;
+    buffer[2] += CC;
+    buffer[3] += DD;
+}
+void md5String(char *input, uint8_t *result){
+    MD5Context ctx;
+    md5Init(&ctx);
+    md5Update(&ctx, (uint8_t *)input, strlen(input));
+    md5Finalize(&ctx);
+
+    memcpy(result, ctx.digest, 16);
+}
+void md5File(FILE *file, uint8_t *result){
+    char *input_buffer = malloc(1024);
+    size_t input_size = 0;
+
+    MD5Context ctx;
+    md5Init(&ctx);
+
+    while((input_size = fread(input_buffer, 1, 1024, file)) > 0){
+        md5Update(&ctx, (uint8_t *)input_buffer, input_size);
+    }
+
+    md5Finalize(&ctx);
+
+    free(input_buffer);
+
+    memcpy(result, ctx.digest, 16);
+}
+
+void toHexString(char* text, char* hex){
+  int len = strlen(text);
+  for (int i = 0, j = 0; i < len; ++i, j += 2)
+    sprintf(hex + j, "%02x", text[i] & 0xff);
+}
+
+#undef A
+#undef B
+#undef C
+#undef D
+
 //===================================================================[list]
 
 void* arrElement(void* head, int i, int size){

+ 18 - 0
la_util.h

@@ -769,6 +769,24 @@ struct barray
     u64bit data[0];
 };
 
+typedef struct{
+    uint64_t size;        // Size of input in bytes
+    uint32_t buffer[4];   // Current accumulation of hash
+    uint8_t input[64];    // Input to be used in the next step
+    uint8_t digest[16];   // Result of algorithm
+}MD5Context;
+
+void md5Init(MD5Context *ctx);
+void md5Update(MD5Context *ctx, uint8_t *input, size_t input_len);
+void md5Finalize(MD5Context *ctx);
+void md5Step(uint32_t *buffer, uint32_t *input);
+
+void md5String(char *input, uint8_t *result);
+void md5File(FILE *file, uint8_t *result);
+
+void toHexString(char* text, char* hex);
+
+
 void laOpenInternetLink(char* url);
 
 #define SEND_PANIC_ERROR(msg) \

+ 35 - 9
resources/la_operators.c

@@ -381,8 +381,33 @@ laFileBrowser *la_FileBrowserInit(laOperator *a){
     la_FileBrowserRebuildList(fb);
     fb->FileName[0] = 0;
 
+    fb->Thumbnail = tnsNewImage(0);
+    fb->ShowThumbnail = 1;
+
     return fb;
 }
+void la_FileBrowserGetFullPath(laFileBrowser *fb,char* buf){
+    buf[0]=0; int plen;
+    if (!fb->SelectFolder && fb->FileName[0] == U'\0') return;
+    plen = strlen(fb->Path);
+    if (fb->Path[plen - 1] != LA_PATH_SEP) strcat(fb->Path, LA_PATH_SEPSTR);
+    strCopyFull(buf, fb->Path);
+    strcat(buf, fb->FileName);
+}
+void la_FileBrowserRefreshThumbnail(laFileBrowser* fb){
+    char buf[2048]="file://"; char md5[128];
+    la_FileBrowserGetFullPath(fb,buf+strlen(buf));
+    md5String(buf,md5); toHexString(md5,fb->MD5);
+    sprintf(buf,"%s/.cache/thumbnails/normal/%s.png", getenv("HOME"),fb->MD5);
+    FILE *fp=fopen(buf,"rb"); char* data=0;
+    if(fp){
+        fseek(fp, 0, SEEK_END); u64bit SeekEnd = ftell(fp); fseek(fp, 0, SEEK_SET);
+        data = calloc(1, SeekEnd); fread(data, SeekEnd, 1, fp);
+        tnsRefreshImage(fb->Thumbnail,data);
+    }else{
+        tnsRefreshImage(fb->Thumbnail,0);
+    }
+}
 void laset_FileBrowserSelectFile(laFileBrowser *fb, laFileItem *fi, int State){
     int len;
     if (fb->Active == fi){
@@ -399,16 +424,9 @@ void laset_FileBrowserSelectFile(laFileBrowser *fb, laFileItem *fi, int State){
             strCopyFull(fb->FileName, fi->Name);
         }
         fb->Active = fi;
+        la_FileBrowserRefreshThumbnail(fb);
     }
 }
-void la_FileBrowserGetFullPath(laFileBrowser *fb,char* buf){
-    buf[0]=0; int plen;
-    if (!fb->SelectFolder && fb->FileName[0] == U'\0') return;
-    plen = strlen(fb->Path);
-    if (fb->Path[plen - 1] != LA_PATH_SEP) strcat(fb->Path, LA_PATH_SEPSTR);
-    strCopyFull(buf, fb->Path);
-    strcat(buf, fb->FileName);
-}
 int la_FileBrowserConfirm(laOperator *a, laFileBrowser *fb){
     char buf[2048];
     la_FileBrowserGetFullPath(fb,buf);
@@ -476,6 +494,9 @@ void* laset_FileBrowserExtension(laFileBrowser* fb, laExtensionType* et){
 void laset_FileBrowserShowBackups(laFileBrowser* fb, int show){
     fb->ShowBackups = show; la_FileBrowserRebuildList(fb); laRecalcCurrentPanel();
 }
+void laset_FileBrowserShowThumbnail(laFileBrowser* fb, int show){
+    fb->ShowThumbnail = show; if(show){ la_FileBrowserRefreshThumbnail(fb); } laRecalcCurrentPanel();
+}
 void laset_FileBrowserSortName(laFileBrowser* fb, int s){
     if(fb->SortBy==LA_FILE_SORT_NAME) fb->SortBy=LA_FILE_SORT_NAME_REV;
     else fb->SortBy=LA_FILE_SORT_NAME; la_FileBrowserRebuildList(fb); laRecalcCurrentPanel();
@@ -550,6 +571,7 @@ void OPEXT_FileBrowser(laOperator *a, int mark){
     while (f=lstPopItem(&fb->FileList)) memFree(f);
     while (f=lstPopItem(&fb->Bookmarks)) memFree(f);
     strDestroyStringSplitor(&fb->ss_filter_extensions);
+    tnsRemoveImage(fb->Thumbnail);
     memFree(fb);
 }
 int OPMOD_FileBrowser(laOperator *a, laEvent *e){
@@ -2420,9 +2442,13 @@ void la_RegisterBuiltinOperators(){
         laAddStringProperty(p, "path", "Path", "Bookmark path", 0, 0, 0, 0, 0, offsetof(laBookmarkedFolder, Path), 0, 0, 0, 0, LA_UDF_LOCAL);
     }
     ep = laAddEnumProperty(pc, "show_backups", "Show Backups", "Show backup files", 0, 0, 0, 0, 0, offsetof(laFileBrowser, ShowBackups), 0, laset_FileBrowserShowBackups, 0, 0, 0, 0, 0, 0, 0,0);{
-        laAddEnumItemAs(ep, "NONE", "None", "Don't show backup files", 0, 0);
+        laAddEnumItemAs(ep, "NONE", "None", "Don't show backup files", 0, U'~');
         laAddEnumItemAs(ep, "SHOWN", "Shown", "Show backup files", 1, U'~');
     }
+    ep = laAddEnumProperty(pc, "show_thumbnail", "Show Thumbnail", "Show Thumbnail", 0, 0, 0, 0, 0, offsetof(laFileBrowser, ShowThumbnail), 0, laset_FileBrowserShowThumbnail, 0, 0, 0, 0, 0, 0, 0,0);{
+        laAddEnumItemAs(ep, "NONE", "None", "Don't show thumbnail files", 0, U'🖼');
+        laAddEnumItemAs(ep, "SHOWN", "Shown", "Show thumbnail files", 1, U'🖼');
+    }
     ep = laAddEnumProperty(pc, "sort_name", "Name", "Sort by names",LA_WIDGET_ENUM_CYCLE, 0, 0, 0, 0, 0, laget_FileBrowserSortName,laset_FileBrowserSortName, 0, 0, 0, 0, 0, 0, 0,0);{
         laAddEnumItemAs(ep, "NONE", "None", "Don't sort", 0, 0);
         laAddEnumItemAs(ep, "SORT", "Sort", "Sort accending", 1, U'🔻');

+ 11 - 1
resources/la_templates.c

@@ -955,7 +955,17 @@ void laui_FileBrowserFileList(laUiList *uil, laPropPack *THIS_UNUSED, laPropPack
     laUiItem* left=laMakeEmptyGroup(uil, cl, "left", 0); ul=left->Page; left->Flags|=LA_UI_FLAGS_NO_DECAL; ul->HeightCoeff=-1;
     ulc=laFirstColumn(ul);
 
-    laShowItem(ul,ulc,Operator,"show_backups")->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+    laFileBrowser* fb=Operator->EndInstance;
+    if(fb){
+        laUiItem* b=laOnConditionThat(ul,ulc,laPropExpression(Operator,"show_thumbnail"));
+        laShowImage(ul,ulc,fb->Thumbnail,5);
+        laEndCondition(ul,b);
+    }
+    
+    r=laBeginRow(ul,ulc,1,0);
+    laShowItemFull(ul,ulc,Operator,"show_thumbnail",0,"text=Thumbnail",0,0)->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+    laShowItemFull(ul,ulc,Operator,"show_backups",0,"text=Backups",0,0)->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+    laEndRow(ul,r);
 
     b=laOnConditionThat(ul,ulc,laPropExpression(Operator,"use_type"));{
         u = laMakeGroup(ul, ulc, "Use Format", 0)->Page; c = laFirstColumn(u);

+ 2 - 1
resources/la_translations.c

@@ -21,7 +21,8 @@
 static const char *entries[]={
 "New Directory","新文件夹",
 "Bytes","字节",
-"Show Backups","显示备份文件",
+"Thumbnail","缩略图",
+"Backups","备份文件",
 "Auto Switch","自动切换",
 "Per screen config:","针对每个屏幕设置:",
 "Will remove input mapping","将删除映射",

+ 17 - 5
resources/la_widgets.c

@@ -192,8 +192,9 @@ int la_SocketGetHeight(laUiItem *ui){
     if(ui->Flags&(LA_UI_SOCKET_LABEL_N|LA_UI_SOCKET_LABEL_S))return 2; return 1;
 }
 int la_ImageGetHeight(laUiItem *ui){
+    tnsImage* im=ui->Extra;
     if(ui->Flags&LA_UI_IMAGE_FULL_W){
-        tnsImage* im=ui->Extra;
+        if(!im->Texture){ return 1; }
         int W=im->Texture->Width, H=im->Texture->Height;
         real CW=MAIN.CurrentWindow->CW-LA_RH*4, CH=MAIN.CurrentWindow->CH-LA_RH*4;
         real ra=1;
@@ -202,8 +203,8 @@ int la_ImageGetHeight(laUiItem *ui){
         W/=ra; H/=ra;
         return H/LA_RH+1;
     }
-    if(!ui->SymbolID) ui->SymbolID=3;
-    return ui->SymbolID;
+    if(ui->SymbolID) return ui->SymbolID;
+    return 1;
 }
 
 int la_ColorSelectorGetMinWidth(laUiItem *ui){
@@ -1503,7 +1504,18 @@ void la_MapperDraw(laUiItem *ui, int h){
 void la_ImageDraw(laUiItem *ui, int h){
     laBoxedTheme *bt = (*ui->Type->Theme);
     tnsImage* im=ui->Extra;
-    if(!im->Texture) return;
+    if(!im->Texture) { tns_ImageToTexture(im); }
+    if(!im->Texture) {
+        tnsUseNoTexture();
+        tnsColor4dv(laThemeColor(bt,LA_BT_BORDER));
+        tnsVertex2d(ui->L, ui->U); tnsVertex2d(ui->R, ui->U);
+        tnsVertex2d(ui->R, ui->B); tnsVertex2d(ui->L, ui->B);
+        tnsPackAs(GL_LINE_LOOP);
+        tnsVertex2d(ui->L, ui->U); tnsVertex2d(ui->R, ui->B);
+        tnsVertex2d(ui->R, ui->U); tnsVertex2d(ui->L, ui->B);
+        tnsPackAs(GL_LINES);
+        return;
+    }
     int W=im->Texture->Width, H=im->Texture->Height;
     int Full=ui->Flags&LA_UI_IMAGE_FULL_W;
     real r=(real)(ui->R-ui->L)/W; if(r<1){W*=r;H*=r;}
@@ -3119,7 +3131,7 @@ int OPMOD_Collection(laOperator *a, laEvent *e) {
   }
   uil = la_DetectUiListRecursive(ToUil, lx, ly, 10000, 0, 0, 0, 0, 0);
   if (uil != ((laUiListDrawItem *)a->LocalUiLists.pFirst)->Target) {
-    printf("%d %d EXIT\n",((laUiListDrawItem *)a->LocalUiLists.pFirst)->Target, uil);
+    //printf("%d %d EXIT\n",((laUiListDrawItem *)a->LocalUiLists.pFirst)->Target, uil);
     return LA_FINISHED;
   }