2828import net .minecraft .util .Formatting ;
2929import net .minecraft .util .Identifier ;
3030
31+ import javax .swing .JFileChooser ;
32+ import javax .swing .filechooser .FileNameExtensionFilter ;
3133import java .awt .Color ;
3234import java .lang .annotation .ElementType ;
3335import java .lang .annotation .Retention ;
@@ -70,7 +72,7 @@ public static class EntryInfo {
7072 String id ;
7173 Text name ;
7274 int index ;
73- ClickableWidget colorButton ;
75+ ClickableWidget functionButton ; // color picker button / explorer button
7476 Tab tab ;
7577 }
7678
@@ -175,7 +177,7 @@ else if (inLimits) {
175177 if (!s .contains ("#" )) s = '#' + s ;
176178 if (!HEXADECIMAL_ONLY .matcher (s ).matches ()) return false ;
177179 try {
178- info .colorButton .setMessage (Text .literal ("⬛" ).setStyle (Style .EMPTY .withColor (Color .decode (info .tempValue ).getRGB ())));
180+ info .functionButton .setMessage (Text .literal ("⬛" ).setStyle (Style .EMPTY .withColor (Color .decode (info .tempValue ).getRGB ())));
179181 } catch (Exception ignored ) {}
180182 }
181183 return true ;
@@ -353,7 +355,9 @@ public void fillList() {
353355 if (e .isSlider ())
354356 widget = new MidnightSliderWidget (width - 160 , 0 , 150 , 20 , Text .of (info .tempValue ), (Double .parseDouble (info .tempValue ) - e .min ()) / (e .max () - e .min ()), info );
355357 else
356- widget = new TextFieldWidget (textRenderer , width - 160 , 0 , 150 , 20 , null , Text .of (info .tempValue ));
358+ widget = new TextFieldWidget (textRenderer ,
359+ width - (160 + (e .selectionMode () > -1 ? 20 : 0 )),
360+ 0 , 150 , 20 , null , Text .of (info .tempValue ));
357361 if (widget instanceof TextFieldWidget textField ) {
358362 textField .setMaxLength (info .width );
359363 textField .setText (info .tempValue );
@@ -363,15 +367,43 @@ public void fillList() {
363367 widget .setTooltip (getTooltip (info ));
364368 if (e .isColor ()) {
365369 resetButton .setWidth (20 );
366- ButtonWidget colorButton = ButtonWidget .builder (Text .literal ("⬛" ), (button -> {
367- })).dimensions (width - 185 , 0 , 20 , 20 ).build ();
370+ ButtonWidget colorButton = ButtonWidget .builder (Text .literal ("⬛" ), (button -> {})).dimensions (width - 185 , 0 , 20 , 20 ).build ();
368371 try {
369372 colorButton .setMessage (Text .literal ("⬛" ).setStyle (Style .EMPTY .withColor (Color .decode (info .tempValue ).getRGB ())));
370373 } catch (Exception ignored ) {}
371- info .colorButton = colorButton ;
374+ info .functionButton = colorButton ;
372375 colorButton .active = false ;
373376 this .list .addButton (List .of (widget , resetButton , colorButton ), name , info );
374- } else this .list .addButton (List .of (widget , resetButton ), name , info );
377+ } else if (e .selectionMode () > -1 ) {
378+ ButtonWidget explorerButton = TextIconButtonWidget .builder (
379+ Text .of ("" ),
380+ button -> {
381+ JFileChooser fileChooser = new JFileChooser ();
382+ fileChooser .setFileSelectionMode (e .selectionMode ());
383+ fileChooser .setDialogType (e .fileChooserType ());
384+ fileChooser .setDialogTitle (Text .translatable (translationPrefix + info .field .getName () + ".fileChooser.title" ).getString ());
385+ if ((e .selectionMode () == JFileChooser .FILES_ONLY || e .selectionMode () == JFileChooser .FILES_AND_DIRECTORIES ) &&
386+ Arrays .stream (e .fileExtensions ()).noneMatch ("*" ::equals )) {
387+ fileChooser .setFileFilter (new FileNameExtensionFilter (
388+ Text .translatable (translationPrefix + info .field .getName () + ".fileFilter.description" ).getString (),
389+ e .fileExtensions ()));
390+ }
391+ if (fileChooser .showDialog (null , null ) == JFileChooser .APPROVE_OPTION ) {
392+ info .value = fileChooser .getSelectedFile ().getAbsolutePath ();
393+ info .tempValue = info .value .toString ();
394+ list .clear ();
395+ fillList ();
396+ }
397+ },
398+ true
399+ ).texture (Identifier .of ("midnightlib" ,"icon/explorer" ), 12 , 12 ).dimension (20 , 20 ).build ();
400+ explorerButton .setPosition (width - 25 , 0 );
401+ resetButton .setWidth (20 );
402+ info .functionButton = explorerButton ;
403+ this .list .addButton (List .of (widget , resetButton , explorerButton ), name , info );
404+ } else {
405+ this .list .addButton (List .of (widget , resetButton ), name , info );
406+ }
375407 } else {
376408 this .list .addButton (List .of (), name , info );
377409 }
@@ -463,16 +495,44 @@ protected void applyValue() {
463495 info .tempValue = String .valueOf (info .value );
464496 }
465497 }
466- @ Retention (RetentionPolicy .RUNTIME ) @ Target (ElementType .FIELD ) public @interface Entry {
467- int width () default 100 ;
498+
499+ /**
500+ * Entry Annotation<br>
501+ * - <b>width</b>: The maximum character length of the {@link String} or {@link List<String>} field<br>
502+ * - <b>min</b>: The minimum value of the <code>int</code>, <code>float</code> or <code>double</code> field<br>
503+ * - <b>max</b>: The maximum value of the <code>int</code>, <code>float</code> or <code>double</code> field<br>
504+ * - <b>name</b>: The name of the field in the config screen<br>
505+ * - <b>selectionMode</b>: The selection mode of the file picker button for {@link String} fields,
506+ * -1 for none, {@link JFileChooser#FILES_ONLY} for files, {@link JFileChooser#DIRECTORIES_ONLY} for directories,
507+ * {@link JFileChooser#FILES_AND_DIRECTORIES} for both (default: -1). Remember to set the translation key
508+ * <code>[modid].midnightconfig.[fieldName].fileChooser.title</code> for the file picker dialog title<br>
509+ * - <b>fileChooserType</b>: The type of the file picker button for {@link String} fields,
510+ * can be {@link JFileChooser#OPEN_DIALOG} or {@link JFileChooser#SAVE_DIALOG} (default: {@link JFileChooser#OPEN_DIALOG}).
511+ * Remember to set the translation key <code>[modid].midnightconfig.[fieldName].fileFilter.description</code> for the file filter description
512+ * if <code>"*"</code> is not used as file extension<br>
513+ * - <b>fileExtensions</b>: The file extensions for the file picker button for {@link String} fields (default: <code>{"*"}</code>),
514+ * only works if selectionMode is {@link JFileChooser#FILES_ONLY} or {@link JFileChooser#FILES_AND_DIRECTORIES}<br>
515+ * - <b>isColor</b>: If the field is a hexadecimal color code (default: false)<br>
516+ * - <b>isSlider</b>: If the field is a slider (default: false)<br>
517+ * - <b>precision</b>: The precision of the <code>float</code> or <code>double</code> field (default: 100)<br>
518+ * - <b>category</b>: The category of the field in the config screen (default: "default")<br>
519+ * */
520+ @ Retention (RetentionPolicy .RUNTIME )
521+ @ Target (ElementType .FIELD )
522+ public @interface Entry {
523+ int width () default 400 ;
468524 double min () default Double .MIN_NORMAL ;
469525 double max () default Double .MAX_VALUE ;
470526 String name () default "" ;
527+ int selectionMode () default -1 ; // -1 for none, 0 for file, 1 for firectory, 2 for both
528+ int fileChooserType () default JFileChooser .OPEN_DIALOG ;
529+ String [] fileExtensions () default {"*" };
471530 boolean isColor () default false ;
472531 boolean isSlider () default false ;
473532 int precision () default 100 ;
474533 String category () default "default" ;
475534 }
535+
476536 @ Retention (RetentionPolicy .RUNTIME ) @ Target (ElementType .FIELD ) public @interface Client {}
477537 @ Retention (RetentionPolicy .RUNTIME ) @ Target (ElementType .FIELD ) public @interface Server {}
478538 @ Retention (RetentionPolicy .RUNTIME ) @ Target (ElementType .FIELD ) public @interface Hidden {}
0 commit comments