Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions jpro-mdfx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ The `-mdfx-code-theme` CSS property controls which TextMate theme is used for sy

Users can point to their own TextMate theme JSON resource.

### Scrollable Code Blocks

Code blocks wrap their content by default. For source code where wrapping makes line structure harder to read, enable horizontal scrolling with `-mdfx-scrollable`:

```css
.markdown-code-block {
-mdfx-scrollable: true;
}
```

When enabled, fenced code blocks keep their original horizontal layout and show a horizontal scrollbar when the code is wider than the available Markdown view width.

## Supported Markdown Features

Headings (H1–H5), bold, italic, strikethrough, links, images, ordered/unordered lists, task lists, fenced code blocks with syntax highlighting, inline code, GFM tables, blockquotes, attributes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package one.jpro.platform.mdfx;

import javafx.css.*;
import javafx.beans.property.BooleanProperty;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
Expand All @@ -23,6 +26,7 @@
public class MarkdownCodeBlock extends StackPane {

private static final String DEFAULT_STYLE_CLASS = "markdown-code-block";
private static final String SCROLLABLE_STYLE_CLASS = "scrollable";
private static final String DEFAULT_CODE_THEME = "/one/jpro/platform/mdfx/themes/github-light-default.json";

private static final Map<String, String> LANGUAGE_TO_GRAMMAR = new HashMap<>();
Expand Down Expand Up @@ -80,15 +84,32 @@ public StyleableProperty<String> getStyleableProperty(MarkdownCodeBlock node) {
}
};

private static final CssMetaData<MarkdownCodeBlock, Boolean> SCROLLABLE =
new CssMetaData<>("-mdfx-scrollable", StyleConverter.getBooleanConverter(), false) {
@Override
public boolean isSettable(MarkdownCodeBlock node) {
return !node.scrollable.isBound();
}

@Override
public StyleableProperty<Boolean> getStyleableProperty(MarkdownCodeBlock node) {
return node.scrollable;
}
};

private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
List<CssMetaData<? extends Styleable, ?>> list = new ArrayList<>(StackPane.getClassCssMetaData());
list.add(CODE_THEME);
list.add(SCROLLABLE);
STYLEABLES = Collections.unmodifiableList(list);
}

private final StyleableStringProperty codeTheme = new SimpleStyleableStringProperty(CODE_THEME, this, "codeTheme", DEFAULT_CODE_THEME);
private final StyleableBooleanProperty scrollable = new SimpleStyleableBooleanProperty(SCROLLABLE, this, "scrollable", false);

private final TextFlow textFlow;
private final ScrollPane scrollPane;
private final String code;
private final String language;
private TextFlowModel textFlowModel;
Expand All @@ -101,11 +122,60 @@ public MarkdownCodeBlock(String code, String language) {

textFlow = new TextFlow();
textFlow.getStyleClass().add("code-text-flow");
getChildren().add(textFlow);

scrollPane = new ScrollPane();
scrollPane.getStyleClass().add("code-scroll-pane");
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(false);
scrollPane.setFocusTraversable(false);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.viewportBoundsProperty().subscribe(bounds -> updateTextFlowMinWidth());

updateScrollableState();

scrollable.subscribe(v -> updateScrollableState());
codeTheme.subscribe(v -> updateContent());
}

public final boolean isScrollable() {
return scrollable.get();
}

public final void setScrollable(boolean scrollable) {
this.scrollable.set(scrollable);
}

public final BooleanProperty scrollableProperty() {
return scrollable;
}

private void updateScrollableState() {
getStyleClass().remove(SCROLLABLE_STYLE_CLASS);
getChildren().clear();

if (isScrollable()) {
getStyleClass().add(SCROLLABLE_STYLE_CLASS);
scrollPane.setContent(textFlow);
getChildren().add(scrollPane);
} else {
scrollPane.setContent(null);
getChildren().add(textFlow);
}

updateTextFlowMinWidth();
}

private void updateTextFlowMinWidth() {
if (isScrollable()) {
textFlow.setMinWidth(scrollPane.getViewportBounds().getWidth());
textFlow.setMinHeight(scrollPane.getViewportBounds().getHeight());
} else {
textFlow.setMinWidth(Region.USE_COMPUTED_SIZE);
textFlow.setMinHeight(Region.USE_COMPUTED_SIZE);
}
}

private void updateContent() {
if (code == null || code.isEmpty()) {
textFlow.getChildren().clear();
Expand Down Expand Up @@ -142,6 +212,10 @@ private void highlightWithTextMate(String code, String grammarResource) {

ThemeSettings settings = styleProvider.getThemeSettings();
StyleHelper.applyThemeSettings(textFlow, settings);
if (settings != null) {
StyleHelper.addOrReplaceStyle(scrollPane, "-fx-background", settings.getBackgroundColor());
StyleHelper.addOrReplaceStyle(scrollPane, "-fx-background-color", settings.getBackgroundColor());
}
} catch (Exception e) {
showPlainText(code);
}
Expand Down
24 changes: 23 additions & 1 deletion jpro-mdfx/src/main/resources/one/jpro/platform/mdfx/mdfx.css
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,26 @@
-fx-background-color: -mdfx-code-block-background;
-fx-line-spacing: 2px;
-fx-font-family: "monospace";
}
}

.markdown-code-block.scrollable .code-scroll-pane {
-fx-padding: 2px;
-fx-border-radius: 8px;
-fx-background-radius: 8px;
-fx-border-color: -mdfx-code-block-border;
-fx-border-width: 1px;
-fx-background-color: -mdfx-code-block-background;
}

.markdown-code-block.scrollable .code-scroll-pane .viewport,
.markdown-code-block.scrollable .code-scroll-pane .corner {
-fx-background-color: transparent;
}

.markdown-code-block.scrollable .code-text-flow {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-border-width: 1px;
-fx-border-radius: 8px;
-fx-background-radius: 8px;
}
Loading