├── .gitignore ├── README.md ├── README_CN.md ├── android ├── AndroidManifest.xml ├── assets │ ├── .gitignore │ ├── font │ │ ├── Coold.ttf │ │ └── xyj.ttf │ ├── html │ │ ├── css │ │ │ └── test.css │ │ ├── img │ │ │ └── bg.jpg │ │ └── test.html │ ├── images │ │ ├── global │ │ │ ├── button.png │ │ │ ├── button_p.png │ │ │ ├── button_p_noborder.png │ │ │ ├── check_off.png │ │ │ ├── check_on.png │ │ │ ├── msg_icon_inner.png │ │ │ ├── msg_icon_outer.png │ │ │ ├── toast_box.png │ │ │ └── white.jpg │ │ └── load │ │ │ └── sprite.png │ └── readme.txt ├── build.gradle ├── ic_launcher-web.png ├── proguard-project.txt ├── project.properties ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ └── values │ │ ├── strings.xml │ │ └── styles.xml └── src │ └── team │ └── rpsg │ └── html │ └── android │ └── AndroidLauncher.java ├── build.gradle ├── core ├── build.gradle └── src │ ├── script │ └── ui │ │ ├── view │ │ └── GameView.groovy │ │ └── widget │ │ └── widget │ │ ├── AsyncLoadImage.java │ │ ├── Button.java │ │ ├── Image.java │ │ ├── Label.java │ │ └── LabelImageCheckbox.java │ └── team │ └── rpsg │ └── htmlTest │ ├── core │ ├── Game.java │ ├── Input.java │ ├── Log.java │ ├── Path.java │ ├── Res.java │ ├── Text.java │ ├── UI.java │ └── Views.java │ ├── ui │ └── view │ │ ├── ParameterizableView.java │ │ └── View.java │ ├── util │ ├── InputProcessorEx.java │ ├── KeyCache.java │ └── SimpleAction.java │ └── view │ └── LoadView.java ├── desktop ├── build.gradle └── src │ └── team │ └── rpsg │ └── html │ └── desktop │ └── DesktopLauncher.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── html ├── build.gradle └── src │ ├── org │ └── jsoup │ │ ├── Connection.java │ │ ├── HttpStatusException.java │ │ ├── Jsoup.java │ │ ├── SerializationException.java │ │ ├── UncheckedIOException.java │ │ ├── UnsupportedMimeTypeException.java │ │ ├── helper │ │ ├── ChangeNotifyingArrayList.java │ │ ├── DataUtil.java │ │ ├── HttpConnection.java │ │ ├── Validate.java │ │ └── W3CDom.java │ │ ├── internal │ │ ├── ConstrainableInputStream.java │ │ ├── Normalizer.java │ │ ├── StringUtil.java │ │ └── package-info.java │ │ ├── nodes │ │ ├── Attribute.java │ │ ├── Attributes.java │ │ ├── BooleanAttribute.java │ │ ├── CDataNode.java │ │ ├── Comment.java │ │ ├── DataNode.java │ │ ├── Document.java │ │ ├── DocumentType.java │ │ ├── Element.java │ │ ├── Entities.java │ │ ├── EntitiesData.java │ │ ├── FormElement.java │ │ ├── LeafNode.java │ │ ├── Node.java │ │ ├── NodeUtils.java │ │ ├── PseudoTextElement.java │ │ ├── TextNode.java │ │ ├── XmlDeclaration.java │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── parser │ │ ├── CharacterReader.java │ │ ├── HtmlTreeBuilder.java │ │ ├── HtmlTreeBuilderState.java │ │ ├── ParseError.java │ │ ├── ParseErrorList.java │ │ ├── ParseSettings.java │ │ ├── Parser.java │ │ ├── Tag.java │ │ ├── Token.java │ │ ├── TokenQueue.java │ │ ├── Tokeniser.java │ │ ├── TokeniserState.java │ │ ├── TreeBuilder.java │ │ ├── XmlTreeBuilder.java │ │ └── package-info.java │ │ ├── safety │ │ ├── Cleaner.java │ │ ├── Whitelist.java │ │ └── package-info.java │ │ └── select │ │ ├── Collector.java │ │ ├── CombiningEvaluator.java │ │ ├── Elements.java │ │ ├── Evaluator.java │ │ ├── NodeFilter.java │ │ ├── NodeTraversor.java │ │ ├── NodeVisitor.java │ │ ├── QueryParser.java │ │ ├── Selector.java │ │ ├── StructuralEvaluator.java │ │ └── package-info.java │ └── team │ └── rpsg │ ├── gdxQuery │ ├── $.java │ ├── Callback.java │ ├── CustomCallback.java │ ├── CustomRunnable.java │ ├── Each.java │ ├── GdxCellQuery.java │ ├── GdxFrame.java │ ├── GdxQuery.java │ ├── GdxQueryRunnable.java │ ├── ListQuery.java │ ├── Map.java │ ├── MapQuery.java │ ├── NullActor.java │ ├── RemoveTest.java │ ├── Test.java │ └── TypedGdxQuery.java │ ├── html │ ├── HTMLStage.groovy │ ├── dom │ │ ├── Dom.groovy │ │ ├── HTMLDom.groovy │ │ ├── HTMLDomPreset.groovy │ │ ├── Root.groovy │ │ ├── UnknownDom.groovy │ │ ├── layout │ │ │ ├── AbstractLayout.groovy │ │ │ ├── InlineLayout.groovy │ │ │ └── TableLayout.groovy │ │ └── node │ │ │ ├── Image.groovy │ │ │ └── Text.groovy │ ├── manager │ │ ├── ResourceManager.groovy │ │ ├── TextManager.groovy │ │ └── widget │ │ │ ├── AsyncLoadImage.java │ │ │ ├── AutoSizeContainer.java │ │ │ ├── Image.java │ │ │ └── Label.java │ └── util │ │ ├── AlignParser.groovy │ │ ├── BoxParser.groovy │ │ ├── ColorParser.groovy │ │ ├── DomParser.groovy │ │ ├── PathParser.groovy │ │ ├── SizeParser.groovy │ │ ├── StyleParser.groovy │ │ └── Timer.java │ └── lazyFont │ └── LazyBitmapFont.java ├── readme ├── show1.png └── show2.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | ## Java 2 | 3 | *.class 4 | *.war 5 | *.ear 6 | hs_err_pid* 7 | 8 | ## GWT 9 | war/ 10 | html/war/gwt_bree/ 11 | html/gwt-unitCache/ 12 | .apt_generated/ 13 | html/war/WEB-INF/deploy/ 14 | html/war/WEB-INF/classes/ 15 | .gwt/ 16 | gwt-unitCache/ 17 | www-test/ 18 | .gwt-tmp/ 19 | 20 | ## Android Studio and Intellij and Android in general 21 | android/libs/armeabi/ 22 | android/libs/armeabi-v7a/ 23 | android/libs/x86/ 24 | android/gen/ 25 | .idea/ 26 | *.ipr 27 | *.iws 28 | *.iml 29 | out/ 30 | com_crashlytics_export_strings.xml 31 | 32 | ## Eclipse 33 | .classpath 34 | .project 35 | .metadata 36 | **/bin/ 37 | tmp/ 38 | *.tmp 39 | *.bak 40 | *.swp 41 | *~.nib 42 | local.properties 43 | .settings/ 44 | .loadpath 45 | .externalToolBuilders/ 46 | *.launch 47 | 48 | ## NetBeans 49 | **/nbproject/private/ 50 | build/ 51 | nbbuild/ 52 | dist/ 53 | nbdist/ 54 | nbactions.xml 55 | nb-configuration.xml 56 | 57 | ## Gradle 58 | 59 | .gradle 60 | gradle-app.setting 61 | build/ 62 | 63 | ## OS Specific 64 | .DS_Store 65 | 66 | ## nutget 67 | packages/ 68 | 69 | ## Visual Studio 70 | obj/ 71 | extension/document/~$系统(程序员).docx -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # GDX-HTML 2 | 3 | 4 | 用HTML+CSS+JS构建libGDX的UI! 5 | 6 | ![Image](https://raw.githubusercontent.com/dingjibang/GDX-HTML/master/readme/show2.png) 7 | ![Image](https://raw.githubusercontent.com/dingjibang/GDX-HTML/master/readme/show1.png) 8 | 9 | 10 | ### 目前项目仍在开发 11 | 12 |
13 | 14 | # 怎么用 15 | 16 | Stage stage = HTMLStage.buildPath(path-to-html-file); 17 | //done! 18 | 19 | # 支持的CSS属性和HTML 20 | 21 | #### ":green_heart:" 标志代表完全支持,而且不损耗性能,建议使用。 22 | ###### (这些属性一般LibGDX里都有,所以基本不损耗性能) 23 | #### ":heart:" 标志代表完全支持,但是稍微损耗性能。 24 | ###### (这些属性的实现都是模拟/兼容出来的,可能稍微损耗点性能,但是这是个构建引擎(也就是将html+css转译为scene2d),渲染还是LibGDX的scene2d渲染,所以所谓的“损耗”也只是在创建stage时多卡顿了0.001秒而已,而真正渲染起来还是丝滑满帧。) 25 | #### ":blue_heart:" 标志着正在实现。 26 | ###### (我正在实现它们呢。) 27 | #### ":broken_heart:" 标志着不支持的属性 / 和浏览器版本的HTML+CSS不同的地方。 28 | ###### (不支持代表他们太浪费性能,或者仅仅是我太菜实现不出来hhhh) 29 |
30 |
31 |
32 | 33 | ## 盒模型 34 | - :green_heart: **width** (px / em) 35 | - :green_heart: **height** (px / em) 36 | - :green_heart: **padding** (padding-left / right / top / bottom) 37 | - :green_heart: **margin** (margin-left(auto) / right(auto) / top / bottom) 38 | 39 | ## 文本控制 40 | - :green_heart: **font-size** (px) 41 | - :green_heart: **color** (color-name / hex / rgb / rgba) 42 | - :green_heart: **text-align** (left / center / right) 43 | - :heart: **line-height** (px) 44 | - ###### 设置“line-height”属性,可能会让界面layout多次。 45 | - ###### 使用“line-height”可能会损耗性能(仅构建过程中)。 46 | - ###### 如果只有单行文本,推荐使用padding或者设置高度的方法,尽量别用line-height。 47 | - :heart: **letter-spacing** (px) 48 | - ###### 设置“letter-spacing”属性,可能会让界面layout多次。 49 | - ###### 使用“letter-spacing”可能会损耗性能(仅构建过程中)。 50 | - :green_heart: **-gdx-markup** (true) 51 | - ###### 开启后,将会支持LibGDX的markup语法,就是用来使用多彩文本的那个东西。 52 | - ###### 当开启后,就无法关闭了,哪怕你设置为false。 53 | - ###### 相比使用一个个div/span,在设置他们的color属性,-gdx-markup会更加轻量,不过用起来费劲就是了。 54 | - :heart: **-gdx-wrap** (true / false) 55 | - ###### 当设置-gdx-wrap为true时,文本将会启用自动换行,但是换行需要给这个文本(或者父元素)一个确定的宽度。 56 | - ###### -gdx-wrap默认是关闭的(false),开启后可能会让界面layout多次,损耗性能(仅构建过程中)。 57 | - ###### :broken_heart: 当你开启了-gdx-wrap后,不 允 许 当前元素再包含任何子元素,这是一个无法实现的问题。如果你想让当前文本颜色是多彩的,可以使用-gdx-markup属性(见上方) 58 | 59 | ## 图片 / 纹理 60 | - :green_heart: **<img async="true" src="..." />** 61 | - :green_heart: **-gdx-image-scaling** 62 | - ###### 图片(<img />)是异步加载的,这样就不会阻塞构建引擎了,但是图片加载完毕之后,会重新layout整个界面,这可能会造成界面“闪”一下,你可以设置<img async="false" src="..." />,这样就会使用同步加载。也可以给img提前设置一个固定的宽高。 63 | - ###### 如果你想给<img />设置Scaling,你可以使用-gdx-image-scaling属性,它的值对应了com.badlogic.gdx.utils.Scaling这个枚举类, 比如 "fit" 或者 "none". 64 | 65 | ## 丰富的背景 66 | - :green_heart: **background-color** (color-name / hex / rgb / rgba) 67 | - :blue_heart: **background-image** 68 | - :blue_heart: **background-position** 69 | - :blue_heart: **background-size** 70 | 71 | ## 元素定位方法(Position) 72 | - :green_heart: **position: static** 73 | - :blue_heart: **position: relative** 74 | - :blue_heart: **position: absolute** 75 | - :blue_heart: **position: fixed** 76 | - :broken_heart: **position: sticky** 77 | 78 | ## 元素实现方法(Display) 79 | - :green_heart: **display: inline** 80 | - :heart: **display: inline-block** 81 | - :heart: **display: block** 82 | - ###### display: block / inline-block的实现方法很蠢,所以在某些情况下,显示的和浏览器不一样。 83 | - :blue_heart: **display: table** 84 | 85 | ## 表格布局(Table) 86 | - :green_heart: **<table />** with **display: table** 87 | - :green_heart: **<tr />** with **display: table-row** 88 | - :green_heart: **<td /> <th />** with **display: table-cell** 89 | - :green_heart: **vertical-align** 90 | - ###### :broken_heart: 在Table里直接放一个div,永远都是非法的,除非你给这个div设置css属性为display: table-cell。否则因此产生的布局问题概不负责。 91 | - ###### :broken_heart: 为了性能,Table不支持margin属性,非要使用的话,可以在外面包围个div,然后给这个div设置padding属性。 92 | - ###### :broken_heart: Table的显示有时候过于玄学,我已经放弃了和浏览器一致性了,所以有时候显示的和浏览器不一样我也没办法了hhhhh。 93 | - ###### :broken_heart: TBody, THead和TR都是假的,他们仅仅用来给表格换一行,不要在上面用css属性。 94 | - ###### 每个单元格(Cell)默认都会延展(expand),也就是宽高不是固定的,是自动计算的,如果这个单元格的宽高和你预想的不一样,你可以给它设置一个固定的宽高。 95 | 96 | ## 字体 97 | - :blue_heart: **font-family** 98 | 99 | ## 边框 100 | - :blue_heart: **border** 101 | 102 | ## 漂浮(Float) 103 | - :broken_heart: **不可能实现的,float是最傻〇的css属性。没有float也能实现所有的东西。** 104 | 105 | ## 选择器(Selector) / 伪选择器 106 | - :blue_heart: **:hover** 107 | - :blue_heart: **:active** 108 | - :green_heart: **:lt / :gt / :eq / :first-child / :last-child** 109 | - :green_heart: **:has(selector) / :not(selector)** 110 | - :green_heart: **:contains(text)** 111 | - :green_heart: **[点我查看](https://jsoup.org/apidocs/org/jsoup/select/Selector.html) 所有支持的选择器 / 伪选择器** 112 | 113 | ## 事件监听(Event listener) 114 | - :blue_heart: Coming soon. 115 | 116 | ## JS脚本(Javascript) 117 | - :blue_heart: Coming soon. 118 | 119 | 120 | ## 更多... 121 | - :green_heart: :green_heart: :green_heart: 122 | 123 | # 代码滞销,帮帮我 124 | - 这项目的饼有点大,可能消化不了,希望有大牛可以帮帮我一起实现_(:3」∠)_ 125 | - 邮箱: dingjibang@qq.com 126 | - QQ: 1406547525 127 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/assets/.gitignore: -------------------------------------------------------------------------------- 1 | /save/ 2 | -------------------------------------------------------------------------------- /android/assets/font/Coold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/font/Coold.ttf -------------------------------------------------------------------------------- /android/assets/font/xyj.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/font/xyj.ttf -------------------------------------------------------------------------------- /android/assets/html/css/test.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: white; 3 | color: #333; 4 | font-family: Microsoft YaHei; 5 | margin: 0; 6 | } 7 | 8 | 9 | .center{ 10 | text-align: center; 11 | padding-top: 150px; 12 | } 13 | .cd{ 14 | display: table; 15 | margin: 0 auto; 16 | } 17 | 18 | .left, .right{ 19 | display: table-cell; 20 | vertical-align: middle; 21 | } 22 | 23 | .left{ 24 | text-align: left; 25 | padding-right: 150px; 26 | } 27 | 28 | .right{ 29 | font-size: 120px; 30 | } 31 | 32 | .title{ 33 | margin-bottom: 10px; 34 | display: inline-block; 35 | font-size: 24px; 36 | } 37 | 38 | .mtitle{ 39 | font-size: 46px; 40 | font-weight: normal; 41 | line-height: 50px; 42 | margin: 0; 43 | } 44 | 45 | .desc{ 46 | padding-top: 50px; 47 | color: #333; 48 | display: inline-block; 49 | } 50 | .desc>span{ 51 | word-break: break-all; 52 | display: block; 53 | padding-top: 10px; 54 | } 55 | 56 | .right div{ 57 | font-size: 26px; 58 | padding: 8px 10px; 59 | } 60 | 61 | span.slo{ 62 | color: #55C0AC; 63 | } -------------------------------------------------------------------------------- /android/assets/html/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/html/img/bg.jpg -------------------------------------------------------------------------------- /android/assets/html/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 | 这是用LibGDX构建的 14 |

页面排版引擎

15 | 16 | GDX-HTML被设计为使用你最熟悉的HTML + CSS + JS来构建LibGDX的UI。 17 | 它支持页面热更新,你可以在运行时构建UI并立即生效,无需重启。 18 | 它尽可能的实现了CSS,和浏览器对齐,但仍有些差距。 19 | 使用GDX-HTML,你可以提高至少10倍UI开发速度。 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /android/assets/images/global/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/button.png -------------------------------------------------------------------------------- /android/assets/images/global/button_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/button_p.png -------------------------------------------------------------------------------- /android/assets/images/global/button_p_noborder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/button_p_noborder.png -------------------------------------------------------------------------------- /android/assets/images/global/check_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/check_off.png -------------------------------------------------------------------------------- /android/assets/images/global/check_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/check_on.png -------------------------------------------------------------------------------- /android/assets/images/global/msg_icon_inner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/msg_icon_inner.png -------------------------------------------------------------------------------- /android/assets/images/global/msg_icon_outer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/msg_icon_outer.png -------------------------------------------------------------------------------- /android/assets/images/global/toast_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/toast_box.png -------------------------------------------------------------------------------- /android/assets/images/global/white.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/global/white.jpg -------------------------------------------------------------------------------- /android/assets/images/load/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/assets/images/load/sprite.png -------------------------------------------------------------------------------- /android/assets/readme.txt: -------------------------------------------------------------------------------- 1 | 秘封异闻录 2 | RPSG制作组 提供娱乐解决方案 3 | http://www.rpsg-team.com 4 | 5 | 敬告: 6 | 请勿在其他地方使用本项目的原创图片,因为画的人不是作者我( 7 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | android { 2 | buildToolsVersion '26.0.2' 3 | compileSdkVersion 22 4 | sourceSets { 5 | main { 6 | manifest.srcFile 'AndroidManifest.xml' 7 | java.srcDirs = ['src'] 8 | aidl.srcDirs = ['src'] 9 | renderscript.srcDirs = ['src'] 10 | res.srcDirs = ['res'] 11 | assets.srcDirs = ['assets'] 12 | jniLibs.srcDirs = ['libs'] 13 | } 14 | 15 | instrumentTest.setRoot('tests') 16 | } 17 | 18 | lintOptions { 19 | abortOnError false 20 | } 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_7 23 | targetCompatibility JavaVersion.VERSION_1_7 24 | } 25 | 26 | 27 | } 28 | // called every time gradle gets executed, takes the native dependencies of 29 | // the natives configuration, and extracts them to the proper libs/ folders 30 | // so they get packed with the APK. 31 | task copyAndroidNatives() { 32 | file("libs/armeabi/").mkdirs(); 33 | file("libs/armeabi-v7a/").mkdirs(); 34 | file("libs/x86/").mkdirs(); 35 | 36 | configurations.natives.files.each { jar -> 37 | def outputDir = null 38 | if (jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a") 39 | if (jar.name.endsWith("natives-armeabi.jar")) outputDir = file("libs/armeabi") 40 | if (jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86") 41 | if (outputDir != null) { 42 | copy { 43 | from zipTree(jar) 44 | into outputDir 45 | include "*.so" 46 | } 47 | } 48 | } 49 | } 50 | task run(type: Exec) { 51 | def path 52 | def localProperties = project.file("../local.properties") 53 | if (localProperties.exists()) { 54 | Properties properties = new Properties() 55 | localProperties.withInputStream { instr -> 56 | properties.load(instr) 57 | } 58 | def sdkDir = properties.getProperty('sdk.dir') 59 | if (sdkDir) { 60 | path = sdkDir 61 | } else { 62 | path = "$System.env.ANDROID_HOME" 63 | } 64 | } else { 65 | path = "$System.env.ANDROID_HOME" 66 | } 67 | 68 | def adb = path + "/platform-tools/adb" 69 | commandLine "$adb", 'shell', 'am', 'start', '-n', 'team.rpsg.rpg.core.android/team.rpsg.rpg.core.android.AndroidLauncher' 70 | } 71 | // sets up the Android Eclipse project, using the old Ant based build. 72 | eclipse { 73 | // need to specify Java source sets explicitely, SpringSource Gradle Eclipse plugin 74 | // ignores any nodes added in classpath.file.withXml 75 | sourceSets { 76 | main { 77 | java.srcDirs "src", 'gen' 78 | } 79 | } 80 | 81 | jdt { 82 | sourceCompatibility = 1.7 83 | targetCompatibility = 1.7 84 | } 85 | 86 | classpath { 87 | plusConfigurations += [project.configurations.compile] 88 | containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES' 89 | } 90 | 91 | project { 92 | name = appName + "-android" 93 | natures 'com.android.ide.eclipse.adt.AndroidNature' 94 | buildCommands.clear(); 95 | buildCommand "com.android.ide.eclipse.adt.ResourceManagerBuilder" 96 | buildCommand "com.android.ide.eclipse.adt.PreCompilerBuilder" 97 | buildCommand "org.eclipse.jdt.core.javabuilder" 98 | buildCommand "com.android.ide.eclipse.adt.ApkBuilder" 99 | } 100 | } 101 | // sets up the Android Idea project, using the old Ant based build. 102 | idea { 103 | module { 104 | sourceDirs += file("src"); 105 | scopes = [COMPILE: [plus: [project.configurations.compile]]] 106 | 107 | iml { 108 | withXml { 109 | def node = it.asNode() 110 | def builder = NodeBuilder.newInstance(); 111 | builder.current = node; 112 | builder.component(name: "FacetManager") { 113 | facet(type: "android", name: "Android") { 114 | configuration { 115 | option(name: "UPDATE_PROPERTY_FILES", value: "true") 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | dependencies { 124 | sourceCompatibility = 1.7 125 | targetCompatibility = 1.7 126 | } -------------------------------------------------------------------------------- /android/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/ic_launcher-web.png -------------------------------------------------------------------------------- /android/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | 22 | -verbose 23 | 24 | -dontwarn android.support.** 25 | -dontwarn com.badlogic.gdx.backends.android.AndroidFragmentApplication 26 | -dontwarn com.badlogic.gdx.utils.GdxBuild 27 | -dontwarn com.badlogic.gdx.physics.box2d.utils.Box2DBuild 28 | -dontwarn com.badlogic.gdx.jnigen.BuildTarget* 29 | -dontwarn com.badlogic.gdx.graphics.g2d.freetype.FreetypeBuild 30 | 31 | -keep class com.badlogic.gdx.controllers.android.AndroidControllers 32 | 33 | -keepclassmembers class com.badlogic.gdx.backends.android.AndroidInput* { 34 | (com.badlogic.gdx.Application, android.content.Context, java.lang.Object, com.badlogic.gdx.backends.android.AndroidApplicationConfiguration); 35 | } 36 | 37 | -keepclassmembers class com.badlogic.gdx.physics.box2d.World { 38 | boolean contactFilter(long, long); 39 | void beginContact(long); 40 | void endContact(long); 41 | void preSolve(long, long); 42 | void postSolve(long, long); 43 | boolean reportFixture(long); 44 | float reportRayFixture(long, float, float, float, float, float); 45 | } 46 | -------------------------------------------------------------------------------- /android/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | -------------------------------------------------------------------------------- /android/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/android/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RPG 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/src/team/rpsg/html/android/AndroidLauncher.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.android; 2 | 3 | import android.os.Bundle; 4 | import com.badlogic.gdx.backends.android.AndroidApplication; 5 | import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; 6 | import team.rpsg.htmlTest.core.Views; 7 | 8 | /** 9 | * android launcher 10 | */ 11 | public class AndroidLauncher extends AndroidApplication { 12 | @Override 13 | protected void onCreate (Bundle savedInstanceState) { 14 | 15 | super.onCreate(savedInstanceState); 16 | AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); 17 | config.numSamples=8; 18 | 19 | initialize(new Views(), config); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:3.0.0' 8 | classpath 'org.codehaus.groovy:groovy-android-gradle-plugin:1.1.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | apply plugin: "eclipse" 14 | apply plugin: "idea" 15 | 16 | version = '1.0' 17 | ext { 18 | appName = "team.rpsg.html" 19 | gdxVersion = '1.9.1' 20 | roboVMVersion = '1.8.0' 21 | ashleyVersion = '1.6.0' 22 | aiVersion = '1.6.0' 23 | } 24 | 25 | repositories { 26 | maven { url 'http://maven.aliyun.com/nexus/content/groups/public/'} 27 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 28 | mavenCentral() 29 | } 30 | 31 | } 32 | 33 | project(":desktop") { 34 | apply plugin: "groovy" 35 | 36 | 37 | dependencies { 38 | compile project(path: ":core") 39 | compile "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion" 40 | compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" 41 | compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" 42 | } 43 | } 44 | 45 | project(":android") { 46 | apply plugin: "android" 47 | apply plugin: 'groovyx.android' 48 | 49 | configurations { natives } 50 | 51 | 52 | dependencies { 53 | compile "org.codehaus.groovy:groovy:2.4.8:grooid" 54 | compile(project(':core')) { 55 | exclude group: 'org.codehaus.groovy' 56 | } 57 | compile "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" 58 | natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi" 59 | natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" 60 | natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" 61 | compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" 62 | natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi" 63 | natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a" 64 | natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86" 65 | } 66 | } 67 | 68 | project(":core") { 69 | apply plugin: "groovy" 70 | 71 | dependencies { 72 | compile project(path: ":html") 73 | compile 'org.codehaus.groovy:groovy:2.4.8' 74 | compile "com.badlogicgames.gdx:gdx:$gdxVersion" 75 | compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" 76 | } 77 | } 78 | 79 | project(":html") { 80 | apply plugin: "groovy" 81 | 82 | dependencies { 83 | compile "org.mozilla:rhino:1.7.8-SNAPSHOT" 84 | compile group: 'net.sourceforge.cssparser', name: 'cssparser', version: '0.9.26' 85 | compile 'org.codehaus.groovy:groovy:2.4.8' 86 | compile "com.badlogicgames.gdx:gdx:$gdxVersion" 87 | compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" 88 | } 89 | } 90 | 91 | tasks.eclipse.doLast { 92 | delete ".project" 93 | } 94 | 95 | eclipse.project { 96 | name = appName + "-core" 97 | } 98 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "groovy" 2 | 3 | sourceCompatibility = 1.8 4 | [compileGroovy, compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 5 | 6 | sourceSets { 7 | main { 8 | java { srcDirs = [] } 9 | groovy { srcDirs = ["src/"] } 10 | } 11 | } -------------------------------------------------------------------------------- /core/src/script/ui/view/GameView.groovy: -------------------------------------------------------------------------------- 1 | package script.ui.view 2 | 3 | 4 | import com.badlogic.gdx.Input 5 | import groovy.transform.CompileStatic 6 | import team.rpsg.html.HTMLStage 7 | import team.rpsg.html.dom.layout.TableLayout 8 | import team.rpsg.htmlTest.core.Game 9 | import team.rpsg.htmlTest.ui.view.View 10 | 11 | @CompileStatic 12 | class GameView extends View{ 13 | 14 | void create() { 15 | init() 16 | } 17 | 18 | void init() { 19 | TableLayout.first =true 20 | if(this.stage != null) 21 | ((HTMLStage)this.stage).dispose() 22 | this.stage = HTMLStage.buildPath("html/test.html") 23 | } 24 | 25 | 26 | void draw(){ 27 | stage.draw() 28 | } 29 | 30 | void act() { 31 | stage.act() 32 | } 33 | 34 | boolean keyDown (int keycode){ 35 | if(keycode == Input.Keys.R) 36 | init() 37 | 38 | super.keyDown(keycode) 39 | } 40 | 41 | void resize() { 42 | if(this.stage != null){ 43 | (this.stage as HTMLStage).resize(Game.width(), Game.height()) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /core/src/script/ui/widget/widget/AsyncLoadImage.java: -------------------------------------------------------------------------------- 1 | package script.ui.widget.widget; 2 | 3 | import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter; 4 | import com.badlogic.gdx.graphics.Texture; 5 | import com.badlogic.gdx.graphics.Texture.TextureFilter; 6 | import com.badlogic.gdx.graphics.g2d.Batch; 7 | import com.badlogic.gdx.graphics.g2d.TextureRegion; 8 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; 9 | import team.rpsg.gdxQuery.CustomRunnable; 10 | import team.rpsg.htmlTest.core.Res; 11 | 12 | /** 13 | * GDX-RPG 异步加载纹理类
14 | * 启用另一个OpenGL线程来达到异步加载纹理的效果,在加载的过程中,当前的图片是无法读取宽/高度的。 15 | */ 16 | public class AsyncLoadImage extends Image{ 17 | 18 | private boolean loaded = false, loading = false; 19 | private int originAlignment; 20 | private String texturePath; 21 | 22 | private CustomRunnable _onLoaded = null; 23 | 24 | public AsyncLoadImage() {} 25 | 26 | public AsyncLoadImage(String texturePath) { 27 | this.texturePath = texturePath; 28 | } 29 | 30 | private AsyncLoadImage(String texturePath, CustomRunnable onLoaded) { 31 | this(texturePath); 32 | this._onLoaded = onLoaded; 33 | } 34 | 35 | /** 36 | * 初始化 37 | */ 38 | private void init(){ 39 | /**如果开启纹理过滤,则设置*/ 40 | ((TextureRegionDrawable)getDrawable()).getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear); 41 | 42 | /**重置自身大小*/ 43 | if (getWidth() == 0) 44 | setWidth(getDrawable().getMinWidth()); 45 | if (getHeight() == 0) 46 | setHeight(getDrawable().getMinHeight()); 47 | if (originAlignment != -1) 48 | setOrigin(originAlignment); 49 | } 50 | 51 | /**当该纹理被调用绘制时,才开始懒加载纹理,否则歇着*/ 52 | public void draw(Batch batch, float parentAlpha) { 53 | super.draw(batch, parentAlpha); 54 | loadTexture(); 55 | } 56 | 57 | TextureParameter parameter = new TextureParameter(); 58 | { 59 | /**当纹理加载完成后的回调*/ 60 | parameter.loadedCallback = (assetManager, fileName, type) -> { 61 | setDrawable(new TextureRegionDrawable(new TextureRegion(assetManager.get(fileName, Texture.class)))); 62 | loaded = true; 63 | init(); 64 | if(_onLoaded != null) 65 | _onLoaded.run(AsyncLoadImage.this); 66 | }; 67 | } 68 | 69 | /**加载纹理**/ 70 | public void loadTexture(){ 71 | if(texturePath == null || loading) 72 | return; 73 | 74 | loading = true; 75 | 76 | Res.assetManager.load(texturePath, Texture.class, parameter); 77 | } 78 | 79 | /**是否加载完成*/ 80 | public boolean loaded(){ 81 | return loaded; 82 | } 83 | 84 | public void setOrigin(int alignment) { 85 | super.setOrigin(originAlignment = alignment); 86 | } 87 | 88 | /** 89 | * 异步加载一个drawable 90 | */ 91 | public void setDrawableAsync(String drawablePath) { 92 | new AsyncLoadImage(drawablePath, that -> { 93 | setDrawable(that.getDrawable()); 94 | init(); 95 | }).loadTexture(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /core/src/script/ui/widget/widget/Button.java: -------------------------------------------------------------------------------- 1 | package script.ui.widget.widget; 2 | 3 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 4 | import team.rpsg.htmlTest.core.Res; 5 | 6 | /** 7 | * GDX-RPG 按钮 8 | */ 9 | public class Button extends com.badlogic.gdx.scenes.scene2d.ui.Button{ 10 | /** 11 | * 快速创建一个按钮,按钮的文件命名为xxx.png & xxx_p.png,其中第一个为默认样式,第二个为按下去时候的样式,第二个不需要传但是要遵守文件命名格式 12 | */ 13 | public Button(String buttonPath){ 14 | super(Res.getDrawable(buttonPath), Res.getDrawable(buttonPath.replaceAll("\\.png", "_p.png"))); 15 | } 16 | 17 | public Button(Drawable up, Drawable down){ 18 | super(up, down); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/src/script/ui/widget/widget/Image.java: -------------------------------------------------------------------------------- 1 | package script.ui.widget.widget; 2 | 3 | import com.badlogic.gdx.graphics.Texture; 4 | import com.badlogic.gdx.graphics.Texture.TextureFilter; 5 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 6 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; 7 | import team.rpsg.gdxQuery.TypedGdxQuery; 8 | import team.rpsg.htmlTest.core.Game; 9 | 10 | /** 11 | * GDX-RPG 图片类 12 | */ 13 | public class Image extends com.badlogic.gdx.scenes.scene2d.ui.Image{ 14 | public TypedGdxQuery query(){ 15 | return new TypedGdxQuery<>(this); 16 | } 17 | 18 | public Image (Texture texture){ 19 | super(texture); 20 | texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); 21 | } 22 | 23 | public void setDrawable(Drawable drawable) { 24 | super.setDrawable(drawable); 25 | 26 | if(drawable != null) 27 | ((TextureRegionDrawable)drawable).getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear); 28 | } 29 | 30 | public Image (Drawable drawable){ 31 | super(drawable); 32 | } 33 | 34 | public Image(){ 35 | super((Drawable)null); 36 | } 37 | 38 | /**使用整数坐标,以免导致纹理失真*/ 39 | public void setWidth(float width) { 40 | super.setWidth((int)width); 41 | } 42 | 43 | public void setHeight(float height) { 44 | super.setHeight((int)height); 45 | } 46 | 47 | public void setX(float x) { 48 | super.setX((int)x); 49 | } 50 | 51 | public void setY(float y) { 52 | super.setY((int)y); 53 | } 54 | 55 | public void setPosition(float x, float y) { 56 | super.setPosition((int)x, (int)y); 57 | } 58 | 59 | public boolean isTransparent() { 60 | return getColor().a <= 0; 61 | } 62 | 63 | public Texture getTexture() { 64 | if(getDrawable() instanceof TextureRegionDrawable) 65 | return ((TextureRegionDrawable) getDrawable()).getRegion().getTexture(); 66 | 67 | return null; 68 | } 69 | 70 | public Image disableTouch(){ 71 | setTouchable(null); 72 | return this; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /core/src/script/ui/widget/widget/Label.java: -------------------------------------------------------------------------------- 1 | package script.ui.widget.widget; 2 | 3 | import com.badlogic.gdx.graphics.g2d.Batch; 4 | import com.badlogic.gdx.scenes.scene2d.Touchable; 5 | import com.badlogic.gdx.utils.Align; 6 | import team.rpsg.gdxQuery.GdxQuery; 7 | import team.rpsg.htmlTest.core.Res; 8 | import team.rpsg.htmlTest.core.Text; 9 | import team.rpsg.lazyFont.LazyBitmapFont; 10 | 11 | /** 12 | * GDX-RPG UI Label
13 | * 在原LibGDX Label的基础上,增加了自动换行、文本超长用“...”省略,以及自动纹理管理的功能。 14 | */ 15 | public class Label extends com.badlogic.gdx.scenes.scene2d.ui.Label{ 16 | 17 | /**是否允许超过长度字尾用“...”显示*/ 18 | private boolean overflow = false; 19 | /**是否允许超过长度自动换行,不能和{@link #overflow}同时为true*/ 20 | private boolean warp = false; 21 | 22 | private CharSequence oldText = "", overflowd = ""; 23 | 24 | /**允许多彩字体*/ 25 | private boolean enableMarkup = false; 26 | 27 | private Integer maxWidth; 28 | 29 | public Label(Object text, LabelStyle style) { 30 | super(text.toString(), style); 31 | setTouchable(Touchable.disabled); 32 | } 33 | 34 | public Label(Object text, int fontsize) { 35 | super(text.toString(), genStyle(fontsize)); 36 | setTouchable(Touchable.disabled); 37 | } 38 | 39 | /**是否启用多彩字体*/ 40 | public Label markup(boolean enable) { 41 | this.enableMarkup = enable; 42 | getStyle().font.getData().markupEnabled = enableMarkup; 43 | return this; 44 | } 45 | 46 | private static LabelStyle genStyle(int fontSize) { 47 | LabelStyle style = new LabelStyle(); 48 | style.font = Res.text.get(fontSize); 49 | return style; 50 | } 51 | 52 | @Override 53 | public void draw(Batch batch, float parentAlpha) { 54 | if (warp && maxWidth != null) { 55 | int width = Text.getTextWidth(getStyle().font, oldText.toString()); 56 | if (width > maxWidth) 57 | setWidth(maxWidth); 58 | } 59 | if (overflow && !oldText.equals(getText().toString())) { 60 | oldText = getText().toString();// cpy 61 | if (Text.getTextWidth(getStyle().font, oldText.toString()) > getWidth()) 62 | for (int i = 0; i < oldText.length(); i++) { 63 | CharSequence sub = oldText.subSequence(0, oldText.length() - i); 64 | int width = Text.getTextWidth(getStyle().font, sub.toString()); 65 | if (width < getWidth()) { 66 | overflowd = (sub.length() <= 3 ? sub : sub.subSequence(0, sub.length() - 3) + "…"); 67 | break; 68 | } 69 | } 70 | else 71 | overflowd = oldText; 72 | } 73 | 74 | if (overflow) { 75 | setText(overflowd); 76 | super.draw(batch, parentAlpha); 77 | setText(oldText); 78 | } else { 79 | super.draw(batch, parentAlpha); 80 | } 81 | } 82 | 83 | 84 | /**是否允许超长换行*/ 85 | public Label warp(boolean warp) { 86 | setWrap(warp); 87 | this.warp = warp; 88 | super.setWrap(warp); 89 | return this; 90 | } 91 | 92 | /**是否允许超长用“...”表示*/ 93 | public Label overflow(boolean hidden) { 94 | this.overflow = hidden; 95 | return this; 96 | } 97 | 98 | public boolean overflow() { 99 | return overflow; 100 | } 101 | 102 | public Label right() { 103 | setAlignment(Align.right); 104 | return this; 105 | } 106 | 107 | public Label center() { 108 | setAlignment(Align.center); 109 | return this; 110 | } 111 | 112 | public Label align(int align) { 113 | setAlignment(align); 114 | return this; 115 | } 116 | 117 | public Label left() { 118 | setAlignment(Align.left); 119 | return this; 120 | } 121 | 122 | public Label text(String text) { 123 | setText(text); 124 | return this; 125 | } 126 | 127 | public Label maxWidth(int i) { 128 | maxWidth = i; 129 | return this; 130 | } 131 | 132 | public GdxQuery query() { 133 | return new GdxQuery(this); 134 | } 135 | 136 | public int prefWidth() { 137 | return Res.text.getTextWidth(getText().toString(), ((LazyBitmapFont)getStyle().font).fontSize); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /core/src/script/ui/widget/widget/LabelImageCheckbox.java: -------------------------------------------------------------------------------- 1 | package script.ui.widget.widget; 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Group; 4 | import team.rpsg.gdxQuery.$; 5 | import team.rpsg.htmlTest.core.Res; 6 | 7 | public class LabelImageCheckbox extends Group{ 8 | 9 | Image downimage, upimage, downbg, upbg; 10 | Label text; 11 | 12 | int padding = 10; 13 | 14 | boolean checked = false; 15 | 16 | Runnable onClick = null; 17 | 18 | public LabelImageCheckbox(String text, int fontSize, Image downimage, Image upimage, Image downbg, Image upbg) { 19 | this.downbg = downbg; 20 | this.downimage = downimage.disableTouch(); 21 | this.upbg = upbg; 22 | this.upimage = upimage.disableTouch(); 23 | this.text = Res.text.getLabel(text, fontSize); 24 | 25 | addActor(downbg); 26 | addActor(upbg); 27 | addActor(downimage); 28 | addActor(upimage); 29 | addActor(this.text); 30 | 31 | $.add(upbg, downbg).click(() -> { 32 | checked(!checked); 33 | 34 | if(onClick != null) 35 | onClick.run(); 36 | }); 37 | 38 | checked(false); 39 | 40 | } 41 | 42 | public LabelImageCheckbox click(Runnable onClick){ 43 | this.onClick = onClick; 44 | return this; 45 | } 46 | 47 | public LabelImageCheckbox padding(int pad){ 48 | this.padding = pad; 49 | sizeChanged(); 50 | return this; 51 | } 52 | 53 | public boolean checked(){ 54 | return checked; 55 | } 56 | 57 | public LabelImageCheckbox checked(boolean flag){ 58 | checked = flag; 59 | 60 | $.add(upbg, upimage).visible(!flag); 61 | $.add(downbg, downimage).visible(flag); 62 | 63 | return this; 64 | } 65 | 66 | protected void sizeChanged() { 67 | int width = text.prefWidth() + (int)downimage.getWidth() + padding; 68 | int x = (int)getWidth() / 2 - width / 2; 69 | text.setSize(text.prefWidth(), getHeight()); 70 | text.setPosition(padding + downimage.getWidth() + x, 0); 71 | 72 | $.add(downimage, upimage).position(x, getHeight() / 2 - downimage.getHeight() / 2); 73 | 74 | $.add(upbg, downbg).size(getWidth(), getHeight()); 75 | 76 | super.sizeChanged(); 77 | } 78 | 79 | protected void positionChanged() { 80 | sizeChanged(); 81 | super.positionChanged(); 82 | } 83 | 84 | public LabelImageCheckbox size(int width, int height){ 85 | setSize(width, height); 86 | return this; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/Game.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.core; 2 | 3 | import com.badlogic.gdx.Application.ApplicationType; 4 | import com.badlogic.gdx.Gdx; 5 | import com.badlogic.gdx.graphics.OrthographicCamera; 6 | import com.badlogic.gdx.scenes.scene2d.Stage; 7 | import com.badlogic.gdx.utils.Scaling; 8 | import com.badlogic.gdx.utils.viewport.ScalingViewport; 9 | import script.ui.view.GameView; 10 | 11 | /** 12 | * 游戏上下文 13 | */ 14 | public class Game { 15 | public static final int STAGE_WIDTH = 1280, STAGE_HEIGHT = 720; 16 | 17 | public static int width(){ 18 | return Gdx.graphics.getWidth(); 19 | } 20 | 21 | public static int height(){ 22 | return Gdx.graphics.getHeight(); 23 | } 24 | 25 | 26 | /** 27 | * 获取一个默认{@link Stage},他将和{@link Views#batch}共用一个画笔。
28 | * 要注意的是,这个画笔的{@link com.badlogic.gdx.graphics.g2d.Batch#setTransformMatrix(com.badlogic.gdx.math.Matrix4) transform}被改变的话,将可能导致接下来绘制的东西坐标出现异常。 29 | */ 30 | public static Stage stage(){ 31 | return new Stage(viewport(), Views.batch); 32 | } 33 | 34 | /** 35 | * 获取一个默认的{@link ScalingViewport Viewport},他将根据玩家设置,来选择缩放是“适应”还是“填充”模式。 36 | */ 37 | public static ScalingViewport viewport(){ 38 | return new ScalingViewport(Scaling.fit, Game.STAGE_WIDTH, Game.STAGE_HEIGHT, new OrthographicCamera()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/Input.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.core; 2 | 3 | import java.util.List; 4 | 5 | import com.badlogic.gdx.InputProcessor; 6 | import team.rpsg.htmlTest.ui.view.View; 7 | 8 | /** 9 | * GDX-RPG 输入管理器 10 | *
11 | * 每次接收到从LibGDX获得的输入回调,他都对{@link Views#views}相应输入进行冒泡 12 | */ 13 | public class Input implements InputProcessor{ 14 | 15 | List views; 16 | 17 | public Input(List views) { 18 | this.views = views; 19 | team.rpsg.htmlTest.core.Log.i("Input[created]"); 20 | } 21 | 22 | @Override 23 | public boolean keyDown(int keycode) { 24 | for(View view : views) 25 | if(!view.keyDown(keycode)) 26 | return false; 27 | return true; 28 | } 29 | 30 | @Override 31 | public boolean keyUp(int keycode) { 32 | for(View view : views) 33 | if(!view.keyUp(keycode)) 34 | return false; 35 | return true; 36 | } 37 | 38 | @Override 39 | public boolean keyTyped(char character) { 40 | for(View view : views) 41 | if(!view.keyTyped(character)) 42 | return false; 43 | return true; 44 | } 45 | 46 | @Override 47 | public boolean touchDown(int screenX, int screenY, int pointer, int button) { 48 | for(View view : views) 49 | if(!view.touchDown(screenX, screenY, pointer, button)) 50 | return false; 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean touchUp(int screenX, int screenY, int pointer, int button) { 56 | for(View view : views) 57 | if(!view.touchUp(screenX, screenY, pointer, button)) 58 | return false; 59 | return true; 60 | } 61 | 62 | @Override 63 | public boolean touchDragged(int screenX, int screenY, int pointer) { 64 | for(View view : views) 65 | if(!view.touchDragged(screenX, screenY, pointer)) 66 | return false; 67 | return true; 68 | } 69 | 70 | @Override 71 | public boolean mouseMoved(int screenX, int screenY) { 72 | for(View view : views) 73 | if(!view.mouseMoved(screenX, screenY)) 74 | return false; 75 | return true; 76 | } 77 | 78 | @Override 79 | public boolean scrolled(int amount) { 80 | for(View view : views) 81 | if(!view.scrolled(amount)) 82 | return false; 83 | return true; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/Log.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.core; 2 | 3 | import com.badlogic.gdx.utils.Logger; 4 | 5 | /** 6 | * GDX-RPG 日志工具类 7 | */ 8 | public class Log { 9 | 10 | private static Logger ilogger = new Logger("GDX-RPG[I]", Logger.INFO); 11 | private static Logger dlogger = new Logger("GDX-RPG[D]", Logger.DEBUG); 12 | private static Logger elogger = new Logger("GDX-RPG[E]", Logger.ERROR); 13 | 14 | /**添加一条 消息 等级的日志*/ 15 | public static void i(Object obj){ 16 | ilogger.info(obj == null ? "[null]" : obj.toString()); 17 | } 18 | 19 | /**添加一条 调试 等级的日志*/ 20 | public static void d(Object obj){ 21 | dlogger.debug(obj == null ? "[null]" : obj.toString()); 22 | } 23 | 24 | /**添加一条 错误 等级的日志*/ 25 | public static void e(Object obj){ 26 | elogger.error(obj == null ? "[null]" : obj.toString()); 27 | } 28 | 29 | /**添加一条 错误 等级的日志*/ 30 | public static void e(Object obj, Throwable error){ 31 | elogger.error(obj == null ? "[null]" : obj.toString(), error); 32 | 33 | String text = obj == null ? "[null]" : obj.toString(); 34 | text += "\n"; 35 | text += "[#ff00ff]" + error.getLocalizedMessage() + "[]"; 36 | for(StackTraceElement ele : error.getStackTrace()) 37 | text += "\n [#fc2929]at " + ele.getClassName() + "." + ele.getMethodName() + ":[][#8493ff]" + ele.getLineNumber() + "[]"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/Path.java: -------------------------------------------------------------------------------- 1 | 2 | package team.rpsg.htmlTest.core; 3 | 4 | 5 | /** 6 | * GDX-RPG 资源路径 7 | */ 8 | public class Path { 9 | //基础路径 10 | public static final String BASE_PATH = ""; 11 | //图片路径 12 | public static final String IMAGE_BASE_PATH = "images"; 13 | 14 | public static final String IMAGE_LOAD = BASE_PATH + IMAGE_BASE_PATH + "/load/"; 15 | public static final String IMAGE_GLOBAL = BASE_PATH + IMAGE_BASE_PATH + "/global/"; 16 | 17 | public static final String MUSIC_BGM = BASE_PATH + "sound/bgm/"; 18 | public static final String MUSIC_SE = BASE_PATH + "sound/se/"; 19 | public static final String MAP = BASE_PATH + "maps/"; 20 | 21 | public static final String IMAGE_MENU = BASE_PATH + IMAGE_BASE_PATH + "/menu/"; 22 | public static final String IMAGE_CG = BASE_PATH + IMAGE_BASE_PATH + "/cg/"; 23 | public static final String IMAGE_ENEMY = BASE_PATH + IMAGE_BASE_PATH + "/enemy/"; 24 | public static final String IMAGE_BATTLE = BASE_PATH + IMAGE_BASE_PATH + "/battle/"; 25 | 26 | 27 | //script 28 | public static final String SCRIPT = BASE_PATH + "script"; 29 | public static final String SCRIPT_MAP = SCRIPT + "/map/"; 30 | public static final String SCRIPT_DATA = SCRIPT +"/data/"; 31 | public static final String SCRIPT_DATA_ITEM = SCRIPT_DATA + "/item/"; 32 | public static final String SCRIPT_DATA_ENEMY = SCRIPT_DATA + "/enemy/"; 33 | public static final String SCRIPT_DATA_ASSOCIATION = SCRIPT_DATA + "/association/"; 34 | public static final String SCRIPT_DATA_ASSOCIATION_SKILL = SCRIPT_DATA_ASSOCIATION + "skill/"; 35 | public static final String SCRIPT_DATA_HERO = SCRIPT_DATA + "/hero/"; 36 | public static final String SCRIPT_DATA_BUFF = SCRIPT_DATA + "/buff/"; 37 | public static final String SCRIPT_DATA_PROP = SCRIPT_DATA + "/prop/"; 38 | public static final String SCRIPT_SYSTEM = SCRIPT + "/system/"; 39 | public static final String SCRIPT_DATA_TASK = SCRIPT_DATA + "/task/"; 40 | public static final String SCRIPT_DATA_ACHIEVEMENT = SCRIPT_DATA + "/achievement/"; 41 | public static final String SCRIPT_DATA_INDEX = SCRIPT_DATA + "/index/"; 42 | public static final String SCRIPT_UI = SCRIPT + "/ui/"; 43 | public static final String SCRIPT_UI_VIEW = SCRIPT_UI + "view/"; 44 | public static final String SCRIPT_UI_WIDGET = SCRIPT_UI + "widget/"; 45 | 46 | // 存档最大页数 47 | public static final int SAVE_FILE_MAX_PAGE = 20; 48 | 49 | //存储目录 50 | public static final String SAVE = "save/"; 51 | 52 | } -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/Res.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.core; 2 | 3 | import com.badlogic.gdx.assets.AssetManager; 4 | import com.badlogic.gdx.graphics.Texture; 5 | import com.badlogic.gdx.graphics.g2d.TextureRegion; 6 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 7 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; 8 | import script.ui.widget.widget.AsyncLoadImage; 9 | import script.ui.widget.widget.Image; 10 | 11 | /** 12 | * GDX-RPG 资源/缓存类 13 | */ 14 | public class Res { 15 | 16 | /**资源管理线程*/ 17 | public static AssetManager assetManager; 18 | /**字体管理*/ 19 | public static Text text; 20 | 21 | /**初始化*/ 22 | public static void init(){ 23 | assetManager = new AssetManager(); 24 | /**添加字体管理器*/ 25 | text = new Text(); 26 | Log.i("Resource[created]"); 27 | } 28 | 29 | /** 30 | * 获取一张图片,它是异步加载的,如果需要同步加载,请调用{@link #sync(String) sync}方法 31 | */ 32 | public static AsyncLoadImage get(String path){ 33 | return new AsyncLoadImage(path); 34 | } 35 | 36 | /** 37 | * 立即获取一张图片 38 | */ 39 | public static Image sync(String path){ 40 | return new Image(getTexture(path)); 41 | } 42 | 43 | /** 44 | * 立即获取一张drawable 45 | */ 46 | public static Drawable getDrawable(String path) { 47 | return new TextureRegionDrawable(new TextureRegion(getTexture(path))); 48 | } 49 | 50 | /** 51 | * 更新资源管理器 52 | */ 53 | public static boolean act(){ 54 | return assetManager.update(); 55 | } 56 | 57 | /** 58 | * 立即获取一张纹理 59 | */ 60 | public static Texture getTexture(String path){ 61 | assetManager.load(path, Texture.class); 62 | while(!assetManager.update()); 63 | return assetManager.get(path, Texture.class); 64 | } 65 | 66 | /** 67 | * 预加载纹理 68 | */ 69 | public static void preInit(String[] paths){ 70 | for(String path : paths) 71 | assetManager.load(path, Texture.class); 72 | } 73 | 74 | /**卸载全部纹理*/ 75 | public static void dispose(){ 76 | assetManager.dispose(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/Text.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.core; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import com.badlogic.gdx.Gdx; 7 | import com.badlogic.gdx.graphics.g2d.BitmapFont; 8 | import com.badlogic.gdx.graphics.g2d.GlyphLayout; 9 | import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; 10 | import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle; 11 | import com.badlogic.gdx.utils.Pools; 12 | import script.ui.widget.widget.Label; 13 | import team.rpsg.lazyFont.LazyBitmapFont; 14 | 15 | /** 16 | * GDX-RPG 字体管理器
17 | * 请在{@link Res#text}访问本类 18 | */ 19 | public class Text { 20 | private Map map = new HashMap<>(); 21 | 22 | public FreeTypeFontGenerator NORMAL_GENERATOR = new FreeTypeFontGenerator(Gdx.files.internal(team.rpsg.htmlTest.core.Path.BASE_PATH + "font/xyj.ttf")); 23 | public FreeTypeFontGenerator ENGLISH_GENERATOR = new FreeTypeFontGenerator(Gdx.files.internal(team.rpsg.htmlTest.core.Path.BASE_PATH + "font/Coold.ttf")); 24 | 25 | public LazyBitmapFont get(int fontSize, FreeTypeFontGenerator gen) { 26 | 27 | LazyBitmapFont font = null; 28 | for(Param k : map.keySet()) 29 | if(k.size == fontSize && k.gen == gen) 30 | font = map.get(k); 31 | 32 | if (font == null) { 33 | font = gen == null ? new LazyBitmapFont(NORMAL_GENERATOR, fontSize) : new LazyBitmapFont(gen, fontSize); 34 | map.put(new Param(fontSize, gen), font); 35 | } 36 | 37 | return font; 38 | } 39 | 40 | public LazyBitmapFont get(int fontSize) { 41 | return get(fontSize, null); 42 | } 43 | 44 | public Label getLabel(int fontSize) { 45 | return getLabel("", fontSize); 46 | } 47 | 48 | public Label getLabel(Object text, int fontSize) { 49 | return getLabel(text, fontSize, null); 50 | } 51 | 52 | public Label getLabel(Object text, int fontSize, FreeTypeFontGenerator gen) { 53 | LabelStyle ls = new LabelStyle(); 54 | ls.font = gen == null ? get(fontSize) : get(fontSize, gen); 55 | 56 | return new Label(text.toString(), ls); 57 | } 58 | 59 | 60 | private static class Param{ 61 | public int size; 62 | public FreeTypeFontGenerator gen; 63 | 64 | public Param(int size, FreeTypeFontGenerator gen) { 65 | this.size = size; 66 | this.gen = gen; 67 | } 68 | 69 | } 70 | 71 | public int getTextWidth(String str, int size) { 72 | return getTextWidth(get(size),str); 73 | 74 | } 75 | 76 | public static int getTextWidth(BitmapFont font, String str){ 77 | GlyphLayout layout = Pools.obtain(GlyphLayout.class); 78 | layout.setText(font, str); 79 | return (int) layout.width; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/UI.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.core; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | import com.badlogic.gdx.graphics.Texture; 5 | import com.badlogic.gdx.graphics.g2d.NinePatch; 6 | import com.badlogic.gdx.graphics.g2d.TextureRegion; 7 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 8 | import com.badlogic.gdx.scenes.scene2d.utils.NinePatchDrawable; 9 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; 10 | import script.ui.widget.widget.Button; 11 | import script.ui.widget.widget.Image; 12 | 13 | /** 14 | * GDX-RPG UI工具类 15 | */ 16 | public class UI { 17 | private static Texture base; 18 | private static NinePatchDrawable button, button_press, button_press_noborder; 19 | 20 | public static Image base(){ 21 | return new Image(base); 22 | } 23 | 24 | public static Drawable baseDrawable(){ 25 | return new TextureRegionDrawable(new TextureRegion(base)); 26 | } 27 | 28 | public static Button button() { 29 | return new Button(button, button_press); 30 | } 31 | 32 | public static T button(T style) { 33 | style.up = button; 34 | style.down = button_press; 35 | return style; 36 | } 37 | 38 | public static T buttonNoBorder(T style) { 39 | style.up = button; 40 | style.down = button_press_noborder; 41 | return style; 42 | } 43 | 44 | public static Button buttonNoBorder() { 45 | return new Button(button, button_press_noborder); 46 | } 47 | 48 | public static void init(){ 49 | base = Res.getTexture(team.rpsg.htmlTest.core.Path.IMAGE_GLOBAL + "white.jpg"); 50 | 51 | button = new NinePatchDrawable(get9(team.rpsg.htmlTest.core.Path.IMAGE_GLOBAL + "button.png")); 52 | button_press = new NinePatchDrawable(get9(team.rpsg.htmlTest.core.Path.IMAGE_GLOBAL + "button_p.png")); 53 | button_press_noborder = new NinePatchDrawable(get9(team.rpsg.htmlTest.core.Path.IMAGE_GLOBAL + "button_p_noborder.png")); 54 | 55 | team.rpsg.htmlTest.core.Log.i("UI[created]"); 56 | } 57 | 58 | private static NinePatch get9(String fname){ 59 | return get9(fname, 2, 2, 1, 1, 12, 12, 12, 12); 60 | } 61 | 62 | private static NinePatch get9(String fname, int wpad, int hpad, int woff, int hoff, int left, int right, int top, int bottom) { 63 | final Texture t = new Texture(Gdx.files.internal(fname)); 64 | t.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); 65 | final int width = t.getWidth() - wpad; 66 | final int height = t.getHeight() - hpad; 67 | return new NinePatch(new TextureRegion(t, woff, hoff, width, height), left, right, top, bottom); 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/core/Views.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.core; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.badlogic.gdx.ApplicationListener; 8 | import com.badlogic.gdx.Gdx; 9 | import com.badlogic.gdx.graphics.GL20; 10 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; 11 | import team.rpsg.gdxQuery.$; 12 | import script.ui.view.GameView; 13 | import team.rpsg.html.util.Timer; 14 | import team.rpsg.htmlTest.ui.view.ParameterizableView; 15 | import team.rpsg.htmlTest.ui.view.View; 16 | 17 | /** 18 | * GDX-RPG 游戏入口 19 | * 20 | *

本类为游戏的入口,

21 | */ 22 | public class Views implements ApplicationListener { 23 | 24 | /**画笔*/ 25 | public static SpriteBatch batch; 26 | /**输入监听器*/ 27 | public static Input input; 28 | /**当前所显示的view*/ 29 | public static List views = new ArrayList<>(); 30 | /**缓存的view,将在下一帧加入到{@link #views}里*/ 31 | private static List insertViews = new ArrayList<>(); 32 | 33 | /**载入视图,当有资源被载入时,该视图将被绘制*/ 34 | public static team.rpsg.htmlTest.view.LoadView loadView; 35 | 36 | /**当游戏被创建*/ 37 | public void create() { 38 | 39 | 40 | //创建资源管理器 41 | Res.init(); 42 | //创建UI工具 43 | UI.init(); 44 | //创建全局画笔 45 | batch = new SpriteBatch(); 46 | //创建输入监听器 47 | Gdx.input.setInputProcessor(input = new Input(views)); 48 | 49 | Log.i(">>> Completed <<<"); 50 | Log.i("====================================================="); 51 | Log.i(""); 52 | 53 | //创建载入动画 54 | loadView = new team.rpsg.htmlTest.view.LoadView(); 55 | loadView.create(); 56 | 57 | //创建LOGO界面 58 | addView(GameView.class); 59 | 60 | 61 | } 62 | 63 | /**游戏主循环*/ 64 | public void render() { 65 | //设置OpenGL清屏颜色 66 | Gdx.gl.glClearColor(0,0,0,1); 67 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 68 | 69 | //查找views里是否有需要被删除的元素 70 | $.removeIf(views, View::removeable, v -> v.removeable(false)); 71 | 72 | //如果insertViews有内容,则加入到views里 73 | if(!insertViews.isEmpty()){ 74 | for(View view : insertViews) 75 | views.add(0, view); 76 | insertViews.clear(); 77 | } 78 | 79 | 80 | //依次遍历view 81 | //创建views的快照进行遍历 82 | try{ 83 | for(int i = views.size() - 1; i >= 0; i--){ 84 | View view = views.get(i); 85 | view.act(); 86 | view.draw(); 87 | } 88 | }catch(Exception e){ 89 | e.printStackTrace(); 90 | } 91 | 92 | loadView.act(); 93 | loadView.draw(); 94 | 95 | } 96 | 97 | public static T addView(Class clz) { 98 | return addView(clz, null); 99 | } 100 | 101 | /**增加一个{@link View}到控制器里*/ 102 | public static T addView(Class clz, Map param){ 103 | try { 104 | T view = null; 105 | if(view instanceof ParameterizableView) 106 | view = clz.getDeclaredConstructor(Map.class).newInstance(param); 107 | else 108 | view = clz.newInstance(); 109 | 110 | view.create(); 111 | 112 | addView(view); 113 | 114 | return view; 115 | } catch (Exception e) { 116 | Log.e("got an exception while rending", e); 117 | } 118 | return null; 119 | } 120 | 121 | public static void addView(View view) { 122 | insertViews.add(0, view); 123 | view.removeable(false); 124 | Log.i("Views << " + view.toString()); 125 | } 126 | 127 | public static View find(Class clz){ 128 | synchronized (views) { 129 | return $.getIf(views, v -> v.getClass().getSuperclass().equals(clz)); 130 | } 131 | } 132 | 133 | public void resize(int width, int height) { 134 | synchronized (views){ 135 | for(View view : views) 136 | view.resize(); 137 | } 138 | } 139 | 140 | public void pause() {} 141 | 142 | public void resume() {} 143 | 144 | public void dispose() {} 145 | 146 | } 147 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/ui/view/ParameterizableView.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.ui.view; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 可以发送启动参数的View 7 | */ 8 | public abstract class ParameterizableView extends View{ 9 | 10 | private Map param; 11 | 12 | public ParameterizableView() {}; 13 | 14 | public ParameterizableView(Map map) { 15 | this.param = map; 16 | } 17 | 18 | public void create() { 19 | create(param); 20 | } 21 | 22 | public abstract void create(Map param); 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/util/InputProcessorEx.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.util; 2 | 3 | import com.badlogic.gdx.InputProcessor; 4 | 5 | public class InputProcessorEx implements InputProcessor{ 6 | 7 | public KeyCache keyCache = new KeyCache(); 8 | 9 | boolean allowBubble = true; 10 | 11 | public void bubble(boolean flag) { 12 | allowBubble = flag; 13 | } 14 | 15 | public boolean keyDown(int keycode) { 16 | return keyCache.keyDown(keycode) && allowBubble; 17 | } 18 | 19 | public boolean keyUp(int keycode) { 20 | return keyCache.keyUp(keycode) && allowBubble; 21 | } 22 | 23 | public boolean keyTyped(char character) { 24 | return allowBubble; 25 | } 26 | 27 | public boolean touchDown(int screenX, int screenY, int pointer, int button) { 28 | return keyCache.touchDown() && allowBubble; 29 | } 30 | 31 | public boolean touchUp(int screenX, int screenY, int pointer, int button) { 32 | return keyCache.touchUp() && allowBubble; 33 | } 34 | 35 | public boolean touchDragged(int screenX, int screenY, int pointer) { 36 | return allowBubble; 37 | } 38 | 39 | public boolean mouseMoved(int screenX, int screenY) { 40 | return allowBubble; 41 | } 42 | 43 | public boolean scrolled(int amount) { 44 | return allowBubble; 45 | } 46 | 47 | public boolean isPress(int keycode) { 48 | return keyCache.isPress(keycode); 49 | } 50 | 51 | public boolean isTouch() { 52 | return keyCache.isTouch(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/util/KeyCache.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class KeyCache { 7 | 8 | List keydown = new ArrayList<>(); 9 | 10 | boolean touch = false; 11 | 12 | public boolean keyDown(int keycode) { 13 | keydown.add(keycode); 14 | return true; 15 | } 16 | 17 | public boolean keyUp(int keycode) { 18 | keydown.remove((Object)keycode); 19 | return true; 20 | } 21 | 22 | public boolean isPress(Integer keycode) { 23 | return keydown.contains(keycode); 24 | } 25 | 26 | public boolean touchDown() { 27 | return touch = true; 28 | } 29 | 30 | public boolean touchUp() { 31 | return !(touch = false); 32 | } 33 | 34 | public boolean isTouch() { 35 | return touch; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/util/SimpleAction.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.util; 2 | 3 | import com.badlogic.gdx.math.Interpolation; 4 | import com.badlogic.gdx.scenes.scene2d.actions.TemporalAction; 5 | 6 | public class SimpleAction extends TemporalAction{ 7 | 8 | float start, end, current; 9 | 10 | public SimpleAction(float start, float end, float duration, Interpolation interpolation) { 11 | super(duration, interpolation); 12 | this.start = this.current = start; 13 | this.end = end; 14 | } 15 | 16 | protected void update(float percent) { 17 | current = start + (end - start) * percent; 18 | } 19 | 20 | public float get() { 21 | return current; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return ((Float)get()).toString(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /core/src/team/rpsg/htmlTest/view/LoadView.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.htmlTest.view; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.badlogic.gdx.Gdx; 7 | import com.badlogic.gdx.graphics.Color; 8 | import com.badlogic.gdx.scenes.scene2d.Group; 9 | import com.badlogic.gdx.scenes.scene2d.actions.Actions; 10 | import script.ui.widget.widget.Image; 11 | import script.ui.widget.widget.Label; 12 | import team.rpsg.gdxQuery.$; 13 | import team.rpsg.htmlTest.core.Game; 14 | import team.rpsg.htmlTest.core.Log; 15 | import team.rpsg.htmlTest.core.Path; 16 | import team.rpsg.htmlTest.core.Res; 17 | import team.rpsg.htmlTest.ui.view.View; 18 | 19 | /** 20 | * 载入动画视图
21 | * {@link LoadView}在一般情况下,是为{@link Res}工作的,当其正在加载东西时,LoadView将在游戏最上层画出加载动画,但也可以手动的调用{@link #start(String)}让其工作。 22 | */ 23 | public class LoadView extends View { 24 | 25 | /**当为false时,本视图将被绘制*/ 26 | private boolean updated = true; 27 | /**id列表*/ 28 | private List idList = new ArrayList<>(); 29 | 30 | //Label fps = new Label("1 ", 17), fpsShadow = new Label(" ", 17); 31 | Group sprite; 32 | 33 | public void create() { 34 | stage = Game.stage(); 35 | 36 | Image image = Res.get(Path.IMAGE_LOAD + "sprite.png"); 37 | image.query().action(Actions.forever(Actions.rotateBy(360, .5f))).position(Game.STAGE_WIDTH - 70, 30).to(stage); 38 | 39 | $.add(sprite = new Group()).addActor(image).a(0).to(stage); 40 | 41 | // $.add(fpsShadow).position(4, Game.STAGE_HEIGHT - 24).color(Color.BLACK).a(0.5f).to(stage); 42 | // $.add(fps).position(3, Game.STAGE_HEIGHT - 23).color(Color.WHITE).a(0.5f).to(stage); 43 | 44 | 45 | Log.i("Load-view[created]"); 46 | } 47 | 48 | public void draw() { 49 | //如果Res正在处理资源,或有自定义资源正在处理,则画图 50 | stage.act(); 51 | stage.draw(); 52 | } 53 | 54 | public void act() { 55 | //更新资源 56 | updated = Res.act(); 57 | if(sprite.getActions().size == 0){ 58 | if(updated && idList.isEmpty() && sprite.getColor().a == 1f) 59 | sprite.addAction(Actions.fadeOut(.3f)); 60 | 61 | if((!updated || !idList.isEmpty()) && sprite.getColor().a == 0) 62 | sprite.addAction(Actions.fadeIn(.3f)); 63 | } 64 | 65 | 66 | // String currentFPS = Gdx.graphics.getFramesPerSecond() + ""; 67 | 68 | // fps.text(currentFPS); 69 | // fpsShadow.text(currentFPS); 70 | } 71 | 72 | public void start(String id) { 73 | idList.add(id); 74 | 75 | sprite.clearActions(); 76 | sprite.addAction(Actions.fadeIn(.3f)); 77 | } 78 | 79 | public void stop(String id) { 80 | idList.remove(id); 81 | } 82 | 83 | public boolean updated() { 84 | return updated; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /desktop/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "java" 2 | sourceSets.main.java.srcDirs = ["src/"] 3 | project.ext.mainClassName = "team.rpsg.html.desktop.DesktopLauncher" 4 | project.ext.assetsDir = new File("../android/assets"); 5 | task run(dependsOn: classes, type: JavaExec) { 6 | main = project.mainClassName 7 | classpath = sourceSets.main.runtimeClasspath 8 | standardInput = System.in 9 | workingDir = project.assetsDir 10 | ignoreExitValue = true 11 | } 12 | task dist(type: Jar) { 13 | from files(sourceSets.main.output.classesDir) 14 | from files(sourceSets.main.output.resourcesDir) 15 | from { configurations.compile.collect { zipTree(it) } } 16 | from files(project.assetsDir); 17 | 18 | manifest { 19 | attributes 'Main-Class': project.mainClassName 20 | } 21 | } 22 | dist.dependsOn classes 23 | eclipse { 24 | project { 25 | name = appName + "-desktop" 26 | linkedResource name: 'assets', type: '2', location: 'PARENT-1-PROJECT_LOC/android/assets' 27 | } 28 | } 29 | task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { 30 | doLast { 31 | def classpath = new XmlParser().parse(file(".classpath")) 32 | new Node(classpath, "classpathentry", [kind: 'src', path: 'assets']); 33 | def writer = new FileWriter(file(".classpath")) 34 | def printer = new XmlNodePrinter(new PrintWriter(writer)) 35 | printer.setPreserveWhitespace(true) 36 | printer.print(classpath) 37 | } 38 | } 39 | dependencies { 40 | } -------------------------------------------------------------------------------- /desktop/src/team/rpsg/html/desktop/DesktopLauncher.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.desktop; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | import com.badlogic.gdx.backends.lwjgl.LwjglApplication; 5 | import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; 6 | import com.badlogic.gdx.backends.lwjgl.LwjglFiles; 7 | import com.badlogic.gdx.graphics.Color; 8 | import team.rpsg.html.dom.Dom; 9 | import team.rpsg.htmlTest.core.Views; 10 | 11 | /** 12 | * desktop launcher 13 | */ 14 | public class DesktopLauncher { 15 | public static void main (String[] arg) { 16 | 17 | LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); 18 | 19 | config.width = 1280; 20 | config.height = 720; 21 | 22 | config.foregroundFPS = 0; // Setting to 0 disables foreground fps throttling 23 | config.backgroundFPS = 0; 24 | 25 | //程序未进入之前,先显示灰色的背景 26 | config.initialBackgroundColor= Color.valueOf("2c2c2c"); 27 | 28 | //预先创建Gdx.files以提前读取配置文件 29 | Gdx.files = new LwjglFiles(); 30 | 31 | //进入入口 32 | new LwjglApplication(new Views(), config); 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Fri Mar 18 18:45:52 CST 2016 16 | #systemProp.http.proxyHost=127.0.0.1 17 | org.gradle.jvmargs=-Xms128m -Xmx512m 18 | org.gradle.daemon=true 19 | org.gradle.configureondemand=true 20 | #systemProp.http.proxyPort=8099 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /html/build.gradle: -------------------------------------------------------------------------------- 1 | group 'team.rpsg.html' 2 | version '1.0' 3 | 4 | apply plugin: 'groovy' 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | compile 'org.codehaus.groovy:groovy-all:2.3.11' 12 | } 13 | 14 | sourceSets { 15 | main { 16 | java { srcDirs = [] } 17 | groovy { srcDirs = ["src/"] } 18 | } 19 | } -------------------------------------------------------------------------------- /html/src/org/jsoup/HttpStatusException.java: -------------------------------------------------------------------------------- 1 | package org.jsoup; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Signals that a HTTP request resulted in a not OK HTTP response. 7 | */ 8 | public class HttpStatusException extends IOException { 9 | private int statusCode; 10 | private String url; 11 | 12 | public HttpStatusException(String message, int statusCode, String url) { 13 | super(message); 14 | this.statusCode = statusCode; 15 | this.url = url; 16 | } 17 | 18 | public int getStatusCode() { 19 | return statusCode; 20 | } 21 | 22 | public String getUrl() { 23 | return url; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return super.toString() + ". Status=" + statusCode + ", URL=" + url; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /html/src/org/jsoup/SerializationException.java: -------------------------------------------------------------------------------- 1 | package org.jsoup; 2 | 3 | /** 4 | * A SerializationException is raised whenever serialization of a DOM element fails. This exception usually wraps an 5 | * {@link java.io.IOException} that may be thrown due to an inaccessible output stream. 6 | */ 7 | public final class SerializationException extends RuntimeException { 8 | /** 9 | * Creates and initializes a new serialization exception with no error message and cause. 10 | */ 11 | public SerializationException() { 12 | super(); 13 | } 14 | 15 | /** 16 | * Creates and initializes a new serialization exception with the given error message and no cause. 17 | * 18 | * @param message 19 | * the error message of the new serialization exception (may be null). 20 | */ 21 | public SerializationException(String message) { 22 | super(message); 23 | } 24 | 25 | /** 26 | * Creates and initializes a new serialization exception with the specified cause and an error message of 27 | * (cause==null ? null : cause.toString()) (which typically contains the class and error message of 28 | * cause). 29 | * 30 | * @param cause 31 | * the cause of the new serialization exception (may be null). 32 | */ 33 | public SerializationException(Throwable cause) { 34 | super(cause); 35 | } 36 | 37 | /** 38 | * Creates and initializes a new serialization exception with the given error message and cause. 39 | * 40 | * @param message 41 | * the error message of the new serialization exception. 42 | * @param cause 43 | * the cause of the new serialization exception. 44 | */ 45 | public SerializationException(String message, Throwable cause) { 46 | super(message, cause); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /html/src/org/jsoup/UncheckedIOException.java: -------------------------------------------------------------------------------- 1 | package org.jsoup; 2 | 3 | import java.io.IOException; 4 | 5 | public class UncheckedIOException extends RuntimeException { 6 | public UncheckedIOException(IOException cause) { 7 | super(cause); 8 | } 9 | 10 | public IOException ioException() { 11 | return (IOException) getCause(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /html/src/org/jsoup/UnsupportedMimeTypeException.java: -------------------------------------------------------------------------------- 1 | package org.jsoup; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Signals that a HTTP response returned a mime type that is not supported. 7 | */ 8 | public class UnsupportedMimeTypeException extends IOException { 9 | private String mimeType; 10 | private String url; 11 | 12 | public UnsupportedMimeTypeException(String message, String mimeType, String url) { 13 | super(message); 14 | this.mimeType = mimeType; 15 | this.url = url; 16 | } 17 | 18 | public String getMimeType() { 19 | return mimeType; 20 | } 21 | 22 | public String getUrl() { 23 | return url; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return super.toString() + ". Mimetype=" + mimeType + ", URL="+url; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /html/src/org/jsoup/helper/ChangeNotifyingArrayList.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.helper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | /** 7 | * Implementation of ArrayList that watches out for changes to the contents. 8 | */ 9 | public abstract class ChangeNotifyingArrayList extends ArrayList { 10 | public ChangeNotifyingArrayList(int initialCapacity) { 11 | super(initialCapacity); 12 | } 13 | 14 | public abstract void onContentsChanged(); 15 | 16 | @Override 17 | public E set(int index, E element) { 18 | onContentsChanged(); 19 | return super.set(index, element); 20 | } 21 | 22 | @Override 23 | public boolean add(E e) { 24 | onContentsChanged(); 25 | return super.add(e); 26 | } 27 | 28 | @Override 29 | public void add(int index, E element) { 30 | onContentsChanged(); 31 | super.add(index, element); 32 | } 33 | 34 | @Override 35 | public E remove(int index) { 36 | onContentsChanged(); 37 | return super.remove(index); 38 | } 39 | 40 | @Override 41 | public boolean remove(Object o) { 42 | onContentsChanged(); 43 | return super.remove(o); 44 | } 45 | 46 | @Override 47 | public void clear() { 48 | onContentsChanged(); 49 | super.clear(); 50 | } 51 | 52 | @Override 53 | public boolean addAll(Collection c) { 54 | onContentsChanged(); 55 | return super.addAll(c); 56 | } 57 | 58 | @Override 59 | public boolean addAll(int index, Collection c) { 60 | onContentsChanged(); 61 | return super.addAll(index, c); 62 | } 63 | 64 | @Override 65 | protected void removeRange(int fromIndex, int toIndex) { 66 | onContentsChanged(); 67 | super.removeRange(fromIndex, toIndex); 68 | } 69 | 70 | @Override 71 | public boolean removeAll(Collection c) { 72 | onContentsChanged(); 73 | return super.removeAll(c); 74 | } 75 | 76 | @Override 77 | public boolean retainAll(Collection c) { 78 | onContentsChanged(); 79 | return super.retainAll(c); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /html/src/org/jsoup/helper/Validate.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.helper; 2 | 3 | /** 4 | * Simple validation methods. Designed for jsoup internal use 5 | */ 6 | public final class Validate { 7 | 8 | private Validate() {} 9 | 10 | /** 11 | * Validates that the object is not null 12 | * @param obj object to test 13 | */ 14 | public static void notNull(Object obj) { 15 | if (obj == null) 16 | throw new IllegalArgumentException("Object must not be null"); 17 | } 18 | 19 | /** 20 | * Validates that the object is not null 21 | * @param obj object to test 22 | * @param msg message to output if validation fails 23 | */ 24 | public static void notNull(Object obj, String msg) { 25 | if (obj == null) 26 | throw new IllegalArgumentException(msg); 27 | } 28 | 29 | /** 30 | * Validates that the value is true 31 | * @param val object to test 32 | */ 33 | public static void isTrue(boolean val) { 34 | if (!val) 35 | throw new IllegalArgumentException("Must be true"); 36 | } 37 | 38 | /** 39 | * Validates that the value is true 40 | * @param val object to test 41 | * @param msg message to output if validation fails 42 | */ 43 | public static void isTrue(boolean val, String msg) { 44 | if (!val) 45 | throw new IllegalArgumentException(msg); 46 | } 47 | 48 | /** 49 | * Validates that the value is false 50 | * @param val object to test 51 | */ 52 | public static void isFalse(boolean val) { 53 | if (val) 54 | throw new IllegalArgumentException("Must be false"); 55 | } 56 | 57 | /** 58 | * Validates that the value is false 59 | * @param val object to test 60 | * @param msg message to output if validation fails 61 | */ 62 | public static void isFalse(boolean val, String msg) { 63 | if (val) 64 | throw new IllegalArgumentException(msg); 65 | } 66 | 67 | /** 68 | * Validates that the array contains no null elements 69 | * @param objects the array to test 70 | */ 71 | public static void noNullElements(Object[] objects) { 72 | noNullElements(objects, "Array must not contain any null objects"); 73 | } 74 | 75 | /** 76 | * Validates that the array contains no null elements 77 | * @param objects the array to test 78 | * @param msg message to output if validation fails 79 | */ 80 | public static void noNullElements(Object[] objects, String msg) { 81 | for (Object obj : objects) 82 | if (obj == null) 83 | throw new IllegalArgumentException(msg); 84 | } 85 | 86 | /** 87 | * Validates that the string is not empty 88 | * @param string the string to test 89 | */ 90 | public static void notEmpty(String string) { 91 | if (string == null || string.length() == 0) 92 | throw new IllegalArgumentException("String must not be empty"); 93 | } 94 | 95 | /** 96 | * Validates that the string is not empty 97 | * @param string the string to test 98 | * @param msg message to output if validation fails 99 | */ 100 | public static void notEmpty(String string, String msg) { 101 | if (string == null || string.length() == 0) 102 | throw new IllegalArgumentException(msg); 103 | } 104 | 105 | /** 106 | Cause a failure. 107 | @param msg message to output. 108 | */ 109 | public static void fail(String msg) { 110 | throw new IllegalArgumentException(msg); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /html/src/org/jsoup/internal/ConstrainableInputStream.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.internal; 2 | 3 | import org.jsoup.helper.Validate; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.net.SocketTimeoutException; 10 | import java.nio.ByteBuffer; 11 | 12 | /** 13 | * A jsoup internal class (so don't use it as there is no contract API) that enables constraints on an Input Stream, 14 | * namely a maximum read size, and the ability to Thread.interrupt() the read. 15 | */ 16 | public final class ConstrainableInputStream extends BufferedInputStream { 17 | private static final int DefaultSize = 1024 * 32; 18 | 19 | private final boolean capped; 20 | private final int maxSize; 21 | private long startTime; 22 | private long timeout = 0; // optional max time of request 23 | private int remaining; 24 | private boolean interrupted; 25 | 26 | private ConstrainableInputStream(InputStream in, int bufferSize, int maxSize) { 27 | super(in, bufferSize); 28 | Validate.isTrue(maxSize >= 0); 29 | this.maxSize = maxSize; 30 | remaining = maxSize; 31 | capped = maxSize != 0; 32 | startTime = System.nanoTime(); 33 | } 34 | 35 | /** 36 | * If this InputStream is not already a ConstrainableInputStream, let it be one. 37 | * @param in the input stream to (maybe) wrap 38 | * @param bufferSize the buffer size to use when reading 39 | * @param maxSize the maximum size to allow to be read. 0 == infinite. 40 | * @return a constrainable input stream 41 | */ 42 | public static ConstrainableInputStream wrap(InputStream in, int bufferSize, int maxSize) { 43 | return in instanceof ConstrainableInputStream 44 | ? (ConstrainableInputStream) in 45 | : new ConstrainableInputStream(in, bufferSize, maxSize); 46 | } 47 | 48 | @Override 49 | public int read(byte[] b, int off, int len) throws IOException { 50 | if (interrupted || capped && remaining <= 0) 51 | return -1; 52 | if (Thread.interrupted()) { 53 | // interrupted latches, because parsePadding() may call twice (and we still want the thread interupt to clear) 54 | interrupted = true; 55 | return -1; 56 | } 57 | if (expired()) 58 | throw new SocketTimeoutException("Read timeout"); 59 | 60 | if (capped && len > remaining) 61 | len = remaining; // don't read more than desired, even if available 62 | 63 | try { 64 | final int read = super.read(b, off, len); 65 | remaining -= read; 66 | return read; 67 | } catch (SocketTimeoutException e) { 68 | return 0; 69 | } 70 | } 71 | 72 | /** 73 | * Reads this inputstream to a ByteBuffer. The supplied max may be less than the inputstream's max, to support 74 | * reading just the first bytes. 75 | */ 76 | public ByteBuffer readToByteBuffer(int max) throws IOException { 77 | Validate.isTrue(max >= 0, "maxSize must be 0 (unlimited) or larger"); 78 | final boolean localCapped = max > 0; // still possibly capped in total stream 79 | final int bufferSize = localCapped && max < DefaultSize ? max : DefaultSize; 80 | final byte[] readBuffer = new byte[bufferSize]; 81 | final ByteArrayOutputStream outStream = new ByteArrayOutputStream(bufferSize); 82 | 83 | int read; 84 | int remaining = max; 85 | 86 | while (true) { 87 | read = read(readBuffer); 88 | if (read == -1) break; 89 | if (localCapped) { // this local byteBuffer cap may be smaller than the overall maxSize (like when reading first bytes) 90 | if (read >= remaining) { 91 | outStream.write(readBuffer, 0, remaining); 92 | break; 93 | } 94 | remaining -= read; 95 | } 96 | outStream.write(readBuffer, 0, read); 97 | } 98 | return ByteBuffer.wrap(outStream.toByteArray()); 99 | } 100 | 101 | @Override 102 | public void reset() throws IOException { 103 | super.reset(); 104 | remaining = maxSize - markpos; 105 | } 106 | 107 | public ConstrainableInputStream timeout(long startTimeNanos, long timeoutMillis) { 108 | this.startTime = startTimeNanos; 109 | this.timeout = timeoutMillis * 1000000; 110 | return this; 111 | } 112 | 113 | private boolean expired() { 114 | if (timeout == 0) 115 | return false; 116 | 117 | final long now = System.nanoTime(); 118 | final long dur = now - startTime; 119 | return (dur > timeout); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /html/src/org/jsoup/internal/Normalizer.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.internal; 2 | 3 | import java.util.Locale; 4 | 5 | /** 6 | * Util methods for normalizing strings. Jsoup internal use only, please don't depend on this API. 7 | */ 8 | public final class Normalizer { 9 | 10 | public static String lowerCase(final String input) { 11 | return input != null ? input.toLowerCase(Locale.ENGLISH) : ""; 12 | } 13 | 14 | public static String normalize(final String input) { 15 | return lowerCase(input).trim(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /html/src/org/jsoup/internal/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Util methods used by Jsoup. Please don't depend on the APIs implemented here as the contents may change without 3 | * notice. 4 | */ 5 | package org.jsoup.internal; 6 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/BooleanAttribute.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | /** 4 | * A boolean attribute that is written out without any value. 5 | * @deprecated just use null values (vs empty string) for booleans. 6 | */ 7 | public class BooleanAttribute extends Attribute { 8 | /** 9 | * Create a new boolean attribute from unencoded (raw) key. 10 | * @param key attribute key 11 | */ 12 | public BooleanAttribute(String key) { 13 | super(key, null); 14 | } 15 | 16 | @Override 17 | protected boolean isBooleanAttribute() { 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/CDataNode.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.UncheckedIOException; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * A Character Data node, to support CDATA sections. 9 | */ 10 | public class CDataNode extends TextNode { 11 | public CDataNode(String text) { 12 | super(text); 13 | } 14 | 15 | @Override 16 | public String nodeName() { 17 | return "#cdata"; 18 | } 19 | 20 | /** 21 | * Get the unencoded, non-normalized text content of this CDataNode. 22 | * @return unencoded, non-normalized text 23 | */ 24 | @Override 25 | public String text() { 26 | return getWholeText(); 27 | } 28 | 29 | @Override 30 | void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { 31 | accum 32 | .append(""); 40 | } catch (IOException e) { 41 | throw new UncheckedIOException(e); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/Comment.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.Jsoup; 4 | import org.jsoup.parser.Parser; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | A comment node. 10 | 11 | @author Jonathan Hedley, jonathan@hedley.net */ 12 | public class Comment extends LeafNode { 13 | private static final String COMMENT_KEY = "comment"; 14 | 15 | /** 16 | Create a new comment node. 17 | @param data The contents of the comment 18 | */ 19 | public Comment(String data) { 20 | value = data; 21 | } 22 | 23 | /** 24 | Create a new comment node. 25 | @param data The contents of the comment 26 | @param baseUri base URI not used. This is a leaf node. 27 | @deprecated 28 | */ 29 | public Comment(String data, String baseUri) { 30 | this(data); 31 | } 32 | 33 | public String nodeName() { 34 | return "#comment"; 35 | } 36 | 37 | /** 38 | Get the contents of the comment. 39 | @return comment content 40 | */ 41 | public String getData() { 42 | return coreValue(); 43 | } 44 | 45 | void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { 46 | if (out.prettyPrint()) 47 | indent(accum, depth, out); 48 | accum 49 | .append(""); 52 | } 53 | 54 | void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} 55 | 56 | @Override 57 | public String toString() { 58 | return outerHtml(); 59 | } 60 | 61 | /** 62 | * Check if this comment looks like an XML Declaration. 63 | * @return true if it looks like, maybe, it's an XML Declaration. 64 | */ 65 | public boolean isXmlDeclaration() { 66 | String data = getData(); 67 | return (data.length() > 1 && (data.startsWith("!") || data.startsWith("?"))); 68 | } 69 | 70 | /** 71 | * Attempt to cast this comment to an XML Declaration note. 72 | * @return an XML declaration if it could be parsed as one, null otherwise. 73 | */ 74 | public XmlDeclaration asXmlDeclaration() { 75 | String data = getData(); 76 | Document doc = Jsoup.parse("<" + data.substring(1, data.length() -1) + ">", baseUri(), Parser.xmlParser()); 77 | XmlDeclaration decl = null; 78 | if (doc.childNodeSize() > 0) { 79 | Element el = doc.child(0); 80 | decl = new XmlDeclaration(NodeUtils.parser(doc).settings().normalizeTag(el.tagName()), data.startsWith("!")); 81 | decl.attributes().addAll(el.attributes()); 82 | } 83 | return decl; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/DataNode.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | A data node, for contents of style, script tags etc, where contents should not show in text(). 7 | 8 | @author Jonathan Hedley, jonathan@hedley.net */ 9 | public class DataNode extends LeafNode { 10 | 11 | /** 12 | Create a new DataNode. 13 | @param data data contents 14 | */ 15 | public DataNode(String data) { 16 | value = data; 17 | } 18 | 19 | /** 20 | Create a new DataNode. 21 | @param data data contents 22 | @param baseUri Unused, Leaf Nodes do not hold base URis 23 | @deprecated use {@link #DataNode(String)} instead 24 | */ 25 | public DataNode(String data, String baseUri) { 26 | this(data); 27 | } 28 | 29 | public String nodeName() { 30 | return "#data"; 31 | } 32 | 33 | /** 34 | Get the data contents of this node. Will be unescaped and with original new lines, space etc. 35 | @return data 36 | */ 37 | public String getWholeData() { 38 | return coreValue(); 39 | } 40 | 41 | /** 42 | * Set the data contents of this node. 43 | * @param data unencoded data 44 | * @return this node, for chaining 45 | */ 46 | public DataNode setWholeData(String data) { 47 | coreValue(data); 48 | return this; 49 | } 50 | 51 | void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { 52 | accum.append(getWholeData()); // data is not escaped in return from data nodes, so " in script, style is plain 53 | } 54 | 55 | void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} 56 | 57 | @Override 58 | public String toString() { 59 | return outerHtml(); 60 | } 61 | 62 | /** 63 | Create a new DataNode from HTML encoded data. 64 | @param encodedData encoded data 65 | @param baseUri bass URI 66 | @return new DataNode 67 | */ 68 | public static DataNode createFromEncoded(String encodedData, String baseUri) { 69 | String data = Entities.unescape(encodedData); 70 | return new DataNode(data); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/DocumentType.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.internal.StringUtil; 4 | import org.jsoup.helper.Validate; 5 | import org.jsoup.nodes.Document.OutputSettings.Syntax; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * A {@code } node. 11 | */ 12 | public class DocumentType extends LeafNode { 13 | // todo needs a bit of a chunky cleanup. this level of detail isn't needed 14 | public static final String PUBLIC_KEY = "PUBLIC"; 15 | public static final String SYSTEM_KEY = "SYSTEM"; 16 | private static final String NAME = "name"; 17 | private static final String PUB_SYS_KEY = "pubSysKey"; // PUBLIC or SYSTEM 18 | private static final String PUBLIC_ID = "publicId"; 19 | private static final String SYSTEM_ID = "systemId"; 20 | // todo: quirk mode from publicId and systemId 21 | 22 | /** 23 | * Create a new doctype element. 24 | * @param name the doctype's name 25 | * @param publicId the doctype's public ID 26 | * @param systemId the doctype's system ID 27 | */ 28 | public DocumentType(String name, String publicId, String systemId) { 29 | Validate.notNull(name); 30 | Validate.notNull(publicId); 31 | Validate.notNull(systemId); 32 | attr(NAME, name); 33 | attr(PUBLIC_ID, publicId); 34 | if (has(PUBLIC_ID)) { 35 | attr(PUB_SYS_KEY, PUBLIC_KEY); 36 | } 37 | attr(SYSTEM_ID, systemId); 38 | } 39 | 40 | /** 41 | * Create a new doctype element. 42 | * @param name the doctype's name 43 | * @param publicId the doctype's public ID 44 | * @param systemId the doctype's system ID 45 | * @param baseUri unused 46 | * @deprecated 47 | */ 48 | public DocumentType(String name, String publicId, String systemId, String baseUri) { 49 | attr(NAME, name); 50 | attr(PUBLIC_ID, publicId); 51 | if (has(PUBLIC_ID)) { 52 | attr(PUB_SYS_KEY, PUBLIC_KEY); 53 | } 54 | attr(SYSTEM_ID, systemId); 55 | } 56 | 57 | /** 58 | * Create a new doctype element. 59 | * @param name the doctype's name 60 | * @param publicId the doctype's public ID 61 | * @param systemId the doctype's system ID 62 | * @param baseUri unused 63 | * @deprecated 64 | */ 65 | public DocumentType(String name, String pubSysKey, String publicId, String systemId, String baseUri) { 66 | attr(NAME, name); 67 | if (pubSysKey != null) { 68 | attr(PUB_SYS_KEY, pubSysKey); 69 | } 70 | attr(PUBLIC_ID, publicId); 71 | attr(SYSTEM_ID, systemId); 72 | } 73 | public void setPubSysKey(String value) { 74 | if (value != null) 75 | attr(PUB_SYS_KEY, value); 76 | } 77 | 78 | @Override 79 | public String nodeName() { 80 | return "#doctype"; 81 | } 82 | 83 | @Override 84 | void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { 85 | if (out.syntax() == Syntax.html && !has(PUBLIC_ID) && !has(SYSTEM_ID)) { 86 | // looks like a html5 doctype, go lowercase for aesthetics 87 | accum.append("'); 100 | } 101 | 102 | @Override 103 | void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { 104 | } 105 | 106 | private boolean has(final String attribute) { 107 | return !StringUtil.isBlank(attr(attribute)); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/FormElement.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.Connection; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.helper.HttpConnection; 6 | import org.jsoup.helper.Validate; 7 | import org.jsoup.parser.Tag; 8 | import org.jsoup.select.Elements; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * A HTML Form Element provides ready access to the form fields/controls that are associated with it. It also allows a 15 | * form to easily be submitted. 16 | */ 17 | public class FormElement extends Element { 18 | private final Elements elements = new Elements(); 19 | 20 | /** 21 | * Create a new, standalone form element. 22 | * 23 | * @param tag tag of this element 24 | * @param baseUri the base URI 25 | * @param attributes initial attributes 26 | */ 27 | public FormElement(Tag tag, String baseUri, Attributes attributes) { 28 | super(tag, baseUri, attributes); 29 | } 30 | 31 | /** 32 | * Get the list of form control elements associated with this form. 33 | * @return form controls associated with this element. 34 | */ 35 | public Elements elements() { 36 | return elements; 37 | } 38 | 39 | /** 40 | * Add a form control element to this form. 41 | * @param element form control to add 42 | * @return this form element, for chaining 43 | */ 44 | public FormElement addElement(Element element) { 45 | elements.add(element); 46 | return this; 47 | } 48 | 49 | @Override 50 | protected void removeChild(Node out) { 51 | super.removeChild(out); 52 | elements.remove(out); 53 | } 54 | 55 | /** 56 | * Prepare to submit this form. A Connection object is created with the request set up from the form values. You 57 | * can then set up other options (like user-agent, timeout, cookies), then execute it. 58 | * @return a connection prepared from the values of this form. 59 | * @throws IllegalArgumentException if the form's absolute action URL cannot be determined. Make sure you pass the 60 | * document's base URI when parsing. 61 | */ 62 | public Connection submit() { 63 | String action = hasAttr("action") ? absUrl("action") : baseUri(); 64 | Validate.notEmpty(action, "Could not determine a form action URL for submit. Ensure you set a base URI when parsing."); 65 | Connection.Method method = attr("method").toUpperCase().equals("POST") ? 66 | Connection.Method.POST : Connection.Method.GET; 67 | 68 | return Jsoup.connect(action) 69 | .data(formData()) 70 | .method(method); 71 | } 72 | 73 | /** 74 | * Get the data that this form submits. The returned list is a copy of the data, and changes to the contents of the 75 | * list will not be reflected in the DOM. 76 | * @return a list of key vals 77 | */ 78 | public List formData() { 79 | ArrayList data = new ArrayList<>(); 80 | 81 | // iterate the form control elements and accumulate their values 82 | for (Element el: elements) { 83 | if (!el.tag().isFormSubmittable()) continue; // contents are form listable, superset of submitable 84 | if (el.hasAttr("disabled")) continue; // skip disabled form inputs 85 | String name = el.attr("name"); 86 | if (name.length() == 0) continue; 87 | String type = el.attr("type"); 88 | 89 | if ("select".equals(el.tagName())) { 90 | Elements options = el.select("option[selected]"); 91 | boolean set = false; 92 | for (Element option: options) { 93 | data.add(HttpConnection.KeyVal.create(name, option.val())); 94 | set = true; 95 | } 96 | if (!set) { 97 | Element option = el.select("option").first(); 98 | if (option != null) 99 | data.add(HttpConnection.KeyVal.create(name, option.val())); 100 | } 101 | } else if ("checkbox".equalsIgnoreCase(type) || "radio".equalsIgnoreCase(type)) { 102 | // only add checkbox or radio if they have the checked attribute 103 | if (el.hasAttr("checked")) { 104 | final String val = el.val().length() > 0 ? el.val() : "on"; 105 | data.add(HttpConnection.KeyVal.create(name, val)); 106 | } 107 | } else { 108 | data.add(HttpConnection.KeyVal.create(name, el.val())); 109 | } 110 | } 111 | return data; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/LeafNode.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.helper.Validate; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | abstract class LeafNode extends Node { 9 | private static final List EmptyNodes = Collections.emptyList(); 10 | 11 | Object value; // either a string value, or an attribute map (in the rare case multiple attributes are set) 12 | 13 | protected final boolean hasAttributes() { 14 | return value instanceof Attributes; 15 | } 16 | 17 | @Override 18 | public final Attributes attributes() { 19 | ensureAttributes(); 20 | return (Attributes) value; 21 | } 22 | 23 | private void ensureAttributes() { 24 | if (!hasAttributes()) { 25 | Object coreValue = value; 26 | Attributes attributes = new Attributes(); 27 | value = attributes; 28 | if (coreValue != null) 29 | attributes.put(nodeName(), (String) coreValue); 30 | } 31 | } 32 | 33 | String coreValue() { 34 | return attr(nodeName()); 35 | } 36 | 37 | void coreValue(String value) { 38 | attr(nodeName(), value); 39 | } 40 | 41 | @Override 42 | public String attr(String key) { 43 | Validate.notNull(key); 44 | if (!hasAttributes()) { 45 | return key.equals(nodeName()) ? (String) value : EmptyString; 46 | } 47 | return super.attr(key); 48 | } 49 | 50 | @Override 51 | public Node attr(String key, String value) { 52 | if (!hasAttributes() && key.equals(nodeName())) { 53 | this.value = value; 54 | } else { 55 | ensureAttributes(); 56 | super.attr(key, value); 57 | } 58 | return this; 59 | } 60 | 61 | @Override 62 | public boolean hasAttr(String key) { 63 | ensureAttributes(); 64 | return super.hasAttr(key); 65 | } 66 | 67 | @Override 68 | public Node removeAttr(String key) { 69 | ensureAttributes(); 70 | return super.removeAttr(key); 71 | } 72 | 73 | @Override 74 | public String absUrl(String key) { 75 | ensureAttributes(); 76 | return super.absUrl(key); 77 | } 78 | 79 | @Override 80 | public String baseUri() { 81 | return hasParent() ? parent().baseUri() : ""; 82 | } 83 | 84 | @Override 85 | protected void doSetBaseUri(String baseUri) { 86 | // noop 87 | } 88 | 89 | @Override 90 | public int childNodeSize() { 91 | return 0; 92 | } 93 | 94 | @Override 95 | protected List ensureChildNodes() { 96 | return EmptyNodes; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/NodeUtils.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.parser.HtmlTreeBuilder; 4 | import org.jsoup.parser.Parser; 5 | 6 | /** 7 | * Internal helpers for Nodes, to keep the actual node APIs relatively clean. A jsoup internal class, so don't use it as 8 | * there is no contract API). 9 | */ 10 | final class NodeUtils { 11 | /** 12 | * Get the output setting for this node, or if this node has no document (or parent), retrieve the default output 13 | * settings 14 | */ 15 | static Document.OutputSettings outputSettings(Node node) { 16 | Document owner = node.ownerDocument(); 17 | return owner != null ? owner.outputSettings() : (new Document("")).outputSettings(); 18 | } 19 | 20 | /** 21 | * Get the parser that was used to make this node, or the default HTML parser if it has no parent. 22 | */ 23 | static Parser parser(Node node) { 24 | Document doc = node.ownerDocument(); 25 | return doc != null && doc.parser() != null ? doc.parser() : new Parser(new HtmlTreeBuilder()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/PseudoTextElement.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.parser.Tag; 4 | 5 | /** 6 | * Represents a {@link TextNode} as an {@link Element}, to enable text nodes to be selected with 7 | * the {@link org.jsoup.select.Selector} {@code :matchText} syntax. 8 | */ 9 | public class PseudoTextElement extends Element { 10 | 11 | public PseudoTextElement(Tag tag, String baseUri, Attributes attributes) { 12 | super(tag, baseUri, attributes); 13 | } 14 | 15 | @Override 16 | void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) { 17 | } 18 | 19 | @Override 20 | void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/TextNode.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.internal.StringUtil; 4 | import org.jsoup.helper.Validate; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | A text node. 10 | 11 | @author Jonathan Hedley, jonathan@hedley.net */ 12 | public class TextNode extends LeafNode { 13 | 14 | /** 15 | Create a new TextNode representing the supplied (unencoded) text). 16 | 17 | @param text raw text 18 | @see #createFromEncoded(String) 19 | */ 20 | public TextNode(String text) { 21 | value = text; 22 | } 23 | 24 | /** 25 | Create a new TextNode representing the supplied (unencoded) text). 26 | 27 | @param text raw text 28 | @param baseUri base uri - ignored for this node type 29 | @see #createFromEncoded(String, String) 30 | @deprecated use {@link TextNode#TextNode(String)} 31 | */ 32 | public TextNode(String text, String baseUri) { 33 | this(text); 34 | } 35 | 36 | public String nodeName() { 37 | return "#text"; 38 | } 39 | 40 | /** 41 | * Get the text content of this text node. 42 | * @return Unencoded, normalised text. 43 | * @see TextNode#getWholeText() 44 | */ 45 | public String text() { 46 | return StringUtil.normaliseWhitespace(getWholeText()); 47 | } 48 | 49 | /** 50 | * Set the text content of this text node. 51 | * @param text unencoded text 52 | * @return this, for chaining 53 | */ 54 | public TextNode text(String text) { 55 | coreValue(text); 56 | return this; 57 | } 58 | 59 | /** 60 | Get the (unencoded) text of this text node, including any newlines and spaces present in the original. 61 | @return text 62 | */ 63 | public String getWholeText() { 64 | return coreValue(); 65 | } 66 | 67 | /** 68 | Test if this text node is blank -- that is, empty or only whitespace (including newlines). 69 | @return true if this document is empty or only whitespace, false if it contains any text content. 70 | */ 71 | public boolean isBlank() { 72 | return StringUtil.isBlank(coreValue()); 73 | } 74 | 75 | /** 76 | * Split this text node into two nodes at the specified string offset. After splitting, this node will contain the 77 | * original text up to the offset, and will have a new text node sibling containing the text after the offset. 78 | * @param offset string offset point to split node at. 79 | * @return the newly created text node containing the text after the offset. 80 | */ 81 | public TextNode splitText(int offset) { 82 | final String text = coreValue(); 83 | Validate.isTrue(offset >= 0, "Split offset must be not be negative"); 84 | Validate.isTrue(offset < text.length(), "Split offset must not be greater than current text length"); 85 | 86 | String head = text.substring(0, offset); 87 | String tail = text.substring(offset); 88 | text(head); 89 | TextNode tailNode = new TextNode(tail); 90 | if (parent() != null) 91 | parent().addChildren(siblingIndex()+1, tailNode); 92 | 93 | return tailNode; 94 | } 95 | 96 | void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { 97 | if (out.prettyPrint() && ((siblingIndex() == 0 && parentNode instanceof Element && ((Element) parentNode).tag().formatAsBlock() && !isBlank()) || (out.outline() && siblingNodes().size()>0 && !isBlank()) )) 98 | indent(accum, depth, out); 99 | 100 | boolean normaliseWhite = out.prettyPrint() && parent() instanceof Element 101 | && !Element.preserveWhitespace(parent()); 102 | Entities.escape(accum, coreValue(), out, false, normaliseWhite, false); 103 | } 104 | 105 | void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {} 106 | 107 | @Override 108 | public String toString() { 109 | return outerHtml(); 110 | } 111 | 112 | /** 113 | * Create a new TextNode from HTML encoded (aka escaped) data. 114 | * @param encodedText Text containing encoded HTML (e.g. &lt;) 115 | * @param baseUri Base uri 116 | * @return TextNode containing unencoded data (e.g. <) 117 | * @deprecated use {@link TextNode#createFromEncoded(String)} instead, as LeafNodes don't carry base URIs. 118 | */ 119 | public static TextNode createFromEncoded(String encodedText, String baseUri) { 120 | String text = Entities.unescape(encodedText); 121 | return new TextNode(text); 122 | } 123 | 124 | /** 125 | * Create a new TextNode from HTML encoded (aka escaped) data. 126 | * @param encodedText Text containing encoded HTML (e.g. &lt;) 127 | * @return TextNode containing unencoded data (e.g. <) 128 | */ 129 | public static TextNode createFromEncoded(String encodedText) { 130 | String text = Entities.unescape(encodedText); 131 | return new TextNode(text); 132 | } 133 | 134 | static String normaliseWhitespace(String text) { 135 | text = StringUtil.normaliseWhitespace(text); 136 | return text; 137 | } 138 | 139 | static String stripLeadingWhitespace(String text) { 140 | return text.replaceFirst("^\\s+", ""); 141 | } 142 | 143 | static boolean lastCharIsWhitespace(StringBuilder sb) { 144 | return sb.length() != 0 && sb.charAt(sb.length() - 1) == ' '; 145 | } 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/XmlDeclaration.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.nodes; 2 | 3 | import org.jsoup.SerializationException; 4 | import org.jsoup.internal.StringUtil; 5 | import org.jsoup.helper.Validate; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * An XML Declaration. 11 | */ 12 | public class XmlDeclaration extends LeafNode { 13 | // todo this impl isn't really right, the data shouldn't be attributes, just a run of text after the name 14 | private final boolean isProcessingInstruction; // "); 83 | } 84 | 85 | void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return outerHtml(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /html/src/org/jsoup/nodes/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | HTML document structure nodes. 3 | */ 4 | package org.jsoup.nodes; -------------------------------------------------------------------------------- /html/src/org/jsoup/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | Contains the main {@link org.jsoup.Jsoup} class, which provides convenient static access to the jsoup functionality. 3 | */ 4 | package org.jsoup; -------------------------------------------------------------------------------- /html/src/org/jsoup/parser/ParseError.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.parser; 2 | 3 | /** 4 | * A Parse Error records an error in the input HTML that occurs in either the tokenisation or the tree building phase. 5 | */ 6 | public class ParseError { 7 | private int pos; 8 | private String errorMsg; 9 | 10 | ParseError(int pos, String errorMsg) { 11 | this.pos = pos; 12 | this.errorMsg = errorMsg; 13 | } 14 | 15 | ParseError(int pos, String errorFormat, Object... args) { 16 | this.errorMsg = String.format(errorFormat, args); 17 | this.pos = pos; 18 | } 19 | 20 | /** 21 | * Retrieve the error message. 22 | * @return the error message. 23 | */ 24 | public String getErrorMessage() { 25 | return errorMsg; 26 | } 27 | 28 | /** 29 | * Retrieves the offset of the error. 30 | * @return error offset within input 31 | */ 32 | public int getPosition() { 33 | return pos; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return pos + ": " + errorMsg; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /html/src/org/jsoup/parser/ParseErrorList.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.parser; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * A container for ParseErrors. 7 | * 8 | * @author Jonathan Hedley 9 | */ 10 | public class ParseErrorList extends ArrayList{ 11 | private static final int INITIAL_CAPACITY = 16; 12 | private final int maxSize; 13 | 14 | ParseErrorList(int initialCapacity, int maxSize) { 15 | super(initialCapacity); 16 | this.maxSize = maxSize; 17 | } 18 | 19 | boolean canAddError() { 20 | return size() < maxSize; 21 | } 22 | 23 | int getMaxSize() { 24 | return maxSize; 25 | } 26 | 27 | public static ParseErrorList noTracking() { 28 | return new ParseErrorList(0, 0); 29 | } 30 | 31 | public static ParseErrorList tracking(int maxSize) { 32 | return new ParseErrorList(INITIAL_CAPACITY, maxSize); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /html/src/org/jsoup/parser/ParseSettings.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.parser; 2 | 3 | import org.jsoup.nodes.Attributes; 4 | 5 | import static org.jsoup.internal.Normalizer.lowerCase; 6 | 7 | /** 8 | * Controls parser settings, to optionally preserve tag and/or attribute name case. 9 | */ 10 | public class ParseSettings { 11 | /** 12 | * HTML default settings: both tag and attribute names are lower-cased during parsing. 13 | */ 14 | public static final ParseSettings htmlDefault; 15 | /** 16 | * Preserve both tag and attribute case. 17 | */ 18 | public static final ParseSettings preserveCase; 19 | 20 | static { 21 | htmlDefault = new ParseSettings(false, false); 22 | preserveCase = new ParseSettings(true, true); 23 | } 24 | 25 | private final boolean preserveTagCase; 26 | private final boolean preserveAttributeCase; 27 | 28 | /** 29 | * Define parsePadding settings. 30 | * @param tag preserve tag case? 31 | * @param attribute preserve attribute name case? 32 | */ 33 | public ParseSettings(boolean tag, boolean attribute) { 34 | preserveTagCase = tag; 35 | preserveAttributeCase = attribute; 36 | } 37 | 38 | public String normalizeTag(String name) { 39 | name = name.trim(); 40 | if (!preserveTagCase) 41 | name = lowerCase(name); 42 | return name; 43 | } 44 | 45 | public String normalizeAttribute(String name) { 46 | name = name.trim(); 47 | if (!preserveAttributeCase) 48 | name = lowerCase(name); 49 | return name; 50 | } 51 | 52 | Attributes normalizeAttributes(Attributes attributes) { 53 | if (!preserveAttributeCase) { 54 | attributes.normalize(); 55 | } 56 | return attributes; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /html/src/org/jsoup/parser/TreeBuilder.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.parser; 2 | 3 | import org.jsoup.helper.Validate; 4 | import org.jsoup.nodes.Attributes; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.nodes.Element; 7 | import org.jsoup.nodes.Node; 8 | 9 | import java.io.Reader; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * @author Jonathan Hedley 15 | */ 16 | abstract class TreeBuilder { 17 | protected Parser parser; 18 | CharacterReader reader; 19 | Tokeniser tokeniser; 20 | protected Document doc; // current doc we are building into 21 | protected ArrayList stack; // the stack of open elements 22 | protected String baseUri; // current base uri, for creating new elements 23 | protected Token currentToken; // currentToken is used only for error tracking. 24 | protected ParseSettings settings; 25 | 26 | private Token.StartTag start = new Token.StartTag(); // start tag to process 27 | private Token.EndTag end = new Token.EndTag(); 28 | abstract ParseSettings defaultSettings(); 29 | 30 | protected void initialiseParse(Reader input, String baseUri, Parser parser) { 31 | Validate.notNull(input, "String input must not be null"); 32 | Validate.notNull(baseUri, "BaseURI must not be null"); 33 | 34 | doc = new Document(baseUri); 35 | doc.parser(parser); 36 | this.parser = parser; 37 | settings = parser.settings(); 38 | reader = new CharacterReader(input); 39 | currentToken = null; 40 | tokeniser = new Tokeniser(reader, parser.getErrors()); 41 | stack = new ArrayList<>(32); 42 | this.baseUri = baseUri; 43 | } 44 | 45 | Document parse(Reader input, String baseUri, Parser parser) { 46 | initialiseParse(input, baseUri, parser); 47 | runParser(); 48 | return doc; 49 | } 50 | 51 | abstract List parseFragment(String inputFragment, Element context, String baseUri, Parser parser); 52 | 53 | protected void runParser() { 54 | while (true) { 55 | Token token = tokeniser.read(); 56 | process(token); 57 | token.reset(); 58 | 59 | if (token.type == Token.TokenType.EOF) 60 | break; 61 | } 62 | } 63 | 64 | protected abstract boolean process(Token token); 65 | 66 | protected boolean processStartTag(String name) { 67 | if (currentToken == start) { // don't recycle an in-use token 68 | return process(new Token.StartTag().name(name)); 69 | } 70 | return process(start.reset().name(name)); 71 | } 72 | 73 | public boolean processStartTag(String name, Attributes attrs) { 74 | if (currentToken == start) { // don't recycle an in-use token 75 | return process(new Token.StartTag().nameAttr(name, attrs)); 76 | } 77 | start.reset(); 78 | start.nameAttr(name, attrs); 79 | return process(start); 80 | } 81 | 82 | protected boolean processEndTag(String name) { 83 | if (currentToken == end) { // don't recycle an in-use token 84 | return process(new Token.EndTag().name(name)); 85 | } 86 | return process(end.reset().name(name)); 87 | } 88 | 89 | 90 | protected Element currentElement() { 91 | int size = stack.size(); 92 | return size > 0 ? stack.get(size-1) : null; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /html/src/org/jsoup/parser/XmlTreeBuilder.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.parser; 2 | 3 | import org.jsoup.helper.Validate; 4 | import org.jsoup.nodes.CDataNode; 5 | import org.jsoup.nodes.Comment; 6 | import org.jsoup.nodes.Document; 7 | import org.jsoup.nodes.DocumentType; 8 | import org.jsoup.nodes.Element; 9 | import org.jsoup.nodes.Node; 10 | import org.jsoup.nodes.TextNode; 11 | import org.jsoup.nodes.XmlDeclaration; 12 | 13 | import java.io.Reader; 14 | import java.io.StringReader; 15 | import java.util.List; 16 | 17 | /** 18 | * Use the {@code XmlTreeBuilder} when you want to parsePadding XML without any of the HTML DOM rules being applied to the 19 | * document. 20 | *

Usage example: {@code Document xmlDoc = Jsoup.parsePadding(html, baseUrl, Parser.xmlParser());}

21 | * 22 | * @author Jonathan Hedley 23 | */ 24 | public class XmlTreeBuilder extends TreeBuilder { 25 | ParseSettings defaultSettings() { 26 | return ParseSettings.preserveCase; 27 | } 28 | 29 | @Override 30 | protected void initialiseParse(Reader input, String baseUri, Parser parser) { 31 | super.initialiseParse(input, baseUri, parser); 32 | stack.add(doc); // place the document onto the stack. differs from HtmlTreeBuilder (not on stack) 33 | doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); 34 | } 35 | 36 | Document parse(Reader input, String baseUri) { 37 | return parse(input, baseUri, new Parser(this)); 38 | } 39 | 40 | Document parse(String input, String baseUri) { 41 | return parse(new StringReader(input), baseUri, new Parser(this)); 42 | } 43 | 44 | @Override 45 | protected boolean process(Token token) { 46 | // start tag, end tag, doctype, comment, character, eof 47 | switch (token.type) { 48 | case StartTag: 49 | insert(token.asStartTag()); 50 | break; 51 | case EndTag: 52 | popStackToClose(token.asEndTag()); 53 | break; 54 | case Comment: 55 | insert(token.asComment()); 56 | break; 57 | case Character: 58 | insert(token.asCharacter()); 59 | break; 60 | case Doctype: 61 | insert(token.asDoctype()); 62 | break; 63 | case EOF: // could put some normalisation here if desired 64 | break; 65 | default: 66 | Validate.fail("Unexpected token type: " + token.type); 67 | } 68 | return true; 69 | } 70 | 71 | private void insertNode(Node node) { 72 | currentElement().appendChild(node); 73 | } 74 | 75 | Element insert(Token.StartTag startTag) { 76 | Tag tag = Tag.valueOf(startTag.name(), settings); 77 | // todo: wonder if for xml parsing, should treat all tags as unknown? because it's not html. 78 | Element el = new Element(tag, baseUri, settings.normalizeAttributes(startTag.attributes)); 79 | insertNode(el); 80 | if (startTag.isSelfClosing()) { 81 | if (!tag.isKnownTag()) // unknown tag, remember this is self closing for output. see above. 82 | tag.setSelfClosing(); 83 | } else { 84 | stack.add(el); 85 | } 86 | return el; 87 | } 88 | 89 | void insert(Token.Comment commentToken) { 90 | Comment comment = new Comment(commentToken.getData()); 91 | Node insert = comment; 92 | if (commentToken.bogus && comment.isXmlDeclaration()) { 93 | // xml declarations are emitted as bogus comments (which is right for html, but not xml) 94 | // so we do a bit of a hack and parsePadding the data as an element to pull the attributes out 95 | XmlDeclaration decl = comment.asXmlDeclaration(); // else, we couldn't parsePadding it as a decl, so leave as a comment 96 | if (decl != null) 97 | insert = decl; 98 | } 99 | insertNode(insert); 100 | } 101 | 102 | void insert(Token.Character token) { 103 | final String data = token.getData(); 104 | insertNode(token.isCData() ? new CDataNode(data) : new TextNode(data)); 105 | } 106 | 107 | void insert(Token.Doctype d) { 108 | DocumentType doctypeNode = new DocumentType(settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier()); 109 | doctypeNode.setPubSysKey(d.getPubSysKey()); 110 | insertNode(doctypeNode); 111 | } 112 | 113 | /** 114 | * If the stack contains an element with this tag's name, pop up the stack to remove the first occurrence. If not 115 | * found, skips. 116 | * 117 | * @param endTag tag to close 118 | */ 119 | private void popStackToClose(Token.EndTag endTag) { 120 | String elName = settings.normalizeTag(endTag.tagName); 121 | Element firstFound = null; 122 | 123 | for (int pos = stack.size() -1; pos >= 0; pos--) { 124 | Element next = stack.get(pos); 125 | if (next.nodeName().equals(elName)) { 126 | firstFound = next; 127 | break; 128 | } 129 | } 130 | if (firstFound == null) 131 | return; // not found, skip 132 | 133 | for (int pos = stack.size() -1; pos >= 0; pos--) { 134 | Element next = stack.get(pos); 135 | stack.remove(pos); 136 | if (next == firstFound) 137 | break; 138 | } 139 | } 140 | 141 | 142 | List parseFragment(String inputFragment, String baseUri, Parser parser) { 143 | initialiseParse(new StringReader(inputFragment), baseUri, parser); 144 | runParser(); 145 | return doc.childNodes(); 146 | } 147 | 148 | List parseFragment(String inputFragment, Element context, String baseUri, Parser parser) { 149 | return parseFragment(inputFragment, baseUri, parser); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /html/src/org/jsoup/parser/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | Contains the HTML parser, tag specifications, and HTML tokeniser. 3 | */ 4 | package org.jsoup.parser; 5 | -------------------------------------------------------------------------------- /html/src/org/jsoup/safety/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | Contains the jsoup HTML cleaner, and whitelist definitions. 3 | */ 4 | package org.jsoup.safety; 5 | -------------------------------------------------------------------------------- /html/src/org/jsoup/select/Collector.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.select; 2 | 3 | import org.jsoup.nodes.Element; 4 | import org.jsoup.nodes.Node; 5 | 6 | import static org.jsoup.select.NodeFilter.FilterResult.CONTINUE; 7 | import static org.jsoup.select.NodeFilter.FilterResult.STOP; 8 | 9 | /** 10 | * Collects a list of elements that match the supplied criteria. 11 | * 12 | * @author Jonathan Hedley 13 | */ 14 | public class Collector { 15 | 16 | private Collector() { 17 | } 18 | 19 | /** 20 | Build a list of elements, by visiting root and every descendant of root, and testing it against the evaluator. 21 | @param eval Evaluator to test elements against 22 | @param root root of tree to descend 23 | @return list of matches; empty if none 24 | */ 25 | public static Elements collect (Evaluator eval, Element root) { 26 | Elements elements = new Elements(); 27 | NodeTraversor.traverse(new Accumulator(root, elements, eval), root); 28 | return elements; 29 | } 30 | 31 | private static class Accumulator implements NodeVisitor { 32 | private final Element root; 33 | private final Elements elements; 34 | private final Evaluator eval; 35 | 36 | Accumulator(Element root, Elements elements, Evaluator eval) { 37 | this.root = root; 38 | this.elements = elements; 39 | this.eval = eval; 40 | } 41 | 42 | public void head(Node node, int depth) { 43 | if (node instanceof Element) { 44 | Element el = (Element) node; 45 | if (eval.matches(root, el)) 46 | elements.add(el); 47 | } 48 | } 49 | 50 | public void tail(Node node, int depth) { 51 | // void 52 | } 53 | } 54 | 55 | public static Element findFirst(Evaluator eval, Element root) { 56 | FirstFinder finder = new FirstFinder(root, eval); 57 | NodeTraversor.filter(finder, root); 58 | return finder.match; 59 | } 60 | 61 | private static class FirstFinder implements NodeFilter { 62 | private final Element root; 63 | private Element match = null; 64 | private final Evaluator eval; 65 | 66 | FirstFinder(Element root, Evaluator eval) { 67 | this.root = root; 68 | this.eval = eval; 69 | } 70 | 71 | @Override 72 | public FilterResult head(Node node, int depth) { 73 | if (node instanceof Element) { 74 | Element el = (Element) node; 75 | if (eval.matches(root, el)) { 76 | match = el; 77 | return STOP; 78 | } 79 | } 80 | return CONTINUE; 81 | } 82 | 83 | @Override 84 | public FilterResult tail(Node node, int depth) { 85 | return CONTINUE; 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /html/src/org/jsoup/select/CombiningEvaluator.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.select; 2 | 3 | import org.jsoup.internal.StringUtil; 4 | import org.jsoup.nodes.Element; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | 10 | /** 11 | * Base combining (and, or) evaluator. 12 | */ 13 | abstract class CombiningEvaluator extends Evaluator { 14 | final ArrayList evaluators; 15 | int num = 0; 16 | 17 | CombiningEvaluator() { 18 | super(); 19 | evaluators = new ArrayList<>(); 20 | } 21 | 22 | CombiningEvaluator(Collection evaluators) { 23 | this(); 24 | this.evaluators.addAll(evaluators); 25 | updateNumEvaluators(); 26 | } 27 | 28 | Evaluator rightMostEvaluator() { 29 | return num > 0 ? evaluators.get(num - 1) : null; 30 | } 31 | 32 | void replaceRightMostEvaluator(Evaluator replacement) { 33 | evaluators.set(num - 1, replacement); 34 | } 35 | 36 | void updateNumEvaluators() { 37 | // used so we don't need to bash on size() for every match test 38 | num = evaluators.size(); 39 | } 40 | 41 | static final class And extends CombiningEvaluator { 42 | And(Collection evaluators) { 43 | super(evaluators); 44 | } 45 | 46 | And(Evaluator... evaluators) { 47 | this(Arrays.asList(evaluators)); 48 | } 49 | 50 | @Override 51 | public boolean matches(Element root, Element node) { 52 | for (int i = 0; i < num; i++) { 53 | Evaluator s = evaluators.get(i); 54 | if (!s.matches(root, node)) 55 | return false; 56 | } 57 | return true; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return StringUtil.join(evaluators, " "); 63 | } 64 | } 65 | 66 | static final class Or extends CombiningEvaluator { 67 | /** 68 | * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR. 69 | * @param evaluators initial OR clause (these are wrapped into an AND evaluator). 70 | */ 71 | Or(Collection evaluators) { 72 | super(); 73 | if (num > 1) 74 | this.evaluators.add(new And(evaluators)); 75 | else // 0 or 1 76 | this.evaluators.addAll(evaluators); 77 | updateNumEvaluators(); 78 | } 79 | 80 | Or(Evaluator... evaluators) { this(Arrays.asList(evaluators)); } 81 | 82 | Or() { 83 | super(); 84 | } 85 | 86 | public void add(Evaluator e) { 87 | evaluators.add(e); 88 | updateNumEvaluators(); 89 | } 90 | 91 | @Override 92 | public boolean matches(Element root, Element node) { 93 | for (int i = 0; i < num; i++) { 94 | Evaluator s = evaluators.get(i); 95 | if (s.matches(root, node)) 96 | return true; 97 | } 98 | return false; 99 | } 100 | 101 | @Override 102 | public String toString() { 103 | return StringUtil.join(evaluators, ", "); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /html/src/org/jsoup/select/NodeFilter.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.select; 2 | 3 | import org.jsoup.nodes.Node; 4 | 5 | /** 6 | * Node filter interface. Provide an implementing class to {@link NodeTraversor} to iterate through nodes. 7 | *

8 | * This interface provides two methods, {@code head} and {@code tail}. The head method is called when the node is first 9 | * seen, and the tail method when all of the node's children have been visited. As an example, head can be used to 10 | * create a start tag for a node, and tail to create the end tag. 11 | *

12 | *

13 | * For every node, the filter has to decide whether to 14 | *

    15 | *
  • continue ({@link FilterResult#CONTINUE}),
  • 16 | *
  • skip all children ({@link FilterResult#SKIP_CHILDREN}),
  • 17 | *
  • skip node entirely ({@link FilterResult#SKIP_ENTIRELY}),
  • 18 | *
  • remove the subtree ({@link FilterResult#REMOVE}),
  • 19 | *
  • interrupt the iteration and return ({@link FilterResult#STOP}).
  • 20 | *
21 | * The difference between {@link FilterResult#SKIP_CHILDREN} and {@link FilterResult#SKIP_ENTIRELY} is that the first 22 | * will invoke {@link NodeFilter#tail(Node, int)} on the node, while the latter will not. 23 | * Within {@link NodeFilter#tail(Node, int)}, both are equivalent to {@link FilterResult#CONTINUE}. 24 | *

25 | */ 26 | public interface NodeFilter { 27 | /** 28 | * Filter decision. 29 | */ 30 | enum FilterResult { 31 | /** Continue processing the tree */ 32 | CONTINUE, 33 | /** Skip the child nodes, but do call {@link NodeFilter#tail(Node, int)} next. */ 34 | SKIP_CHILDREN, 35 | /** Skip the subtree, and do not call {@link NodeFilter#tail(Node, int)}. */ 36 | SKIP_ENTIRELY, 37 | /** Remove the node and its children */ 38 | REMOVE, 39 | /** Stop processing */ 40 | STOP 41 | } 42 | 43 | /** 44 | * Callback for when a node is first visited. 45 | * @param node the node being visited. 46 | * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1. 47 | * @return Filter decision 48 | */ 49 | FilterResult head(Node node, int depth); 50 | 51 | /** 52 | * Callback for when a node is last visited, after all of its descendants have been visited. 53 | * @param node the node being visited. 54 | * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1. 55 | * @return Filter decision 56 | */ 57 | FilterResult tail(Node node, int depth); 58 | } 59 | -------------------------------------------------------------------------------- /html/src/org/jsoup/select/NodeTraversor.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.select; 2 | 3 | import org.jsoup.helper.Validate; 4 | import org.jsoup.nodes.Element; 5 | import org.jsoup.nodes.Node; 6 | import org.jsoup.select.NodeFilter.FilterResult; 7 | 8 | /** 9 | * Depth-first node traversor. Use to iterate through all nodes under and including the specified root node. 10 | *

11 | * This implementation does not use recursion, so a deep DOM does not risk blowing the stack. 12 | *

13 | */ 14 | public class NodeTraversor { 15 | private NodeVisitor visitor; 16 | 17 | /** 18 | * Create a new traversor. 19 | * @param visitor a class implementing the {@link NodeVisitor} interface, to be called when visiting each node. 20 | * @deprecated Just use the static {@link NodeTraversor#filter(NodeFilter, Node)} method. 21 | */ 22 | public NodeTraversor(NodeVisitor visitor) { 23 | this.visitor = visitor; 24 | } 25 | 26 | /** 27 | * Start a depth-first traverse of the root and all of its descendants. 28 | * @param root the root node point to traverse. 29 | * @deprecated Just use the static {@link NodeTraversor#filter(NodeFilter, Node)} method. 30 | */ 31 | public void traverse(Node root) { 32 | traverse(visitor, root); 33 | } 34 | 35 | /** 36 | * Start a depth-first traverse of the root and all of its descendants. 37 | * @param visitor Node visitor. 38 | * @param root the root node point to traverse. 39 | */ 40 | public static void traverse(NodeVisitor visitor, Node root) { 41 | Node node = root; 42 | int depth = 0; 43 | 44 | while (node != null) { 45 | visitor.head(node, depth); 46 | if (node.childNodeSize() > 0) { 47 | node = node.childNode(0); 48 | depth++; 49 | } else { 50 | while (node.nextSibling() == null && depth > 0) { 51 | visitor.tail(node, depth); 52 | node = node.parentNode(); 53 | depth--; 54 | } 55 | visitor.tail(node, depth); 56 | if (node == root) 57 | break; 58 | node = node.nextSibling(); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Start a depth-first traverse of all elements. 65 | * @param visitor Node visitor. 66 | * @param elements Elements to filter. 67 | */ 68 | public static void traverse(NodeVisitor visitor, Elements elements) { 69 | Validate.notNull(visitor); 70 | Validate.notNull(elements); 71 | for (Element el : elements) 72 | traverse(visitor, el); 73 | } 74 | 75 | /** 76 | * Start a depth-first filtering of the root and all of its descendants. 77 | * @param filter Node visitor. 78 | * @param root the root node point to traverse. 79 | * @return The filter result of the root node, or {@link FilterResult#STOP}. 80 | */ 81 | public static FilterResult filter(NodeFilter filter, Node root) { 82 | Node node = root; 83 | int depth = 0; 84 | 85 | while (node != null) { 86 | FilterResult result = filter.head(node, depth); 87 | if (result == FilterResult.STOP) 88 | return result; 89 | // Descend into child nodes: 90 | if (result == FilterResult.CONTINUE && node.childNodeSize() > 0) { 91 | node = node.childNode(0); 92 | ++depth; 93 | continue; 94 | } 95 | // No siblings, move upwards: 96 | while (node.nextSibling() == null && depth > 0) { 97 | // 'tail' current node: 98 | if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) { 99 | result = filter.tail(node, depth); 100 | if (result == FilterResult.STOP) 101 | return result; 102 | } 103 | Node prev = node; // In case we need to remove it below. 104 | node = node.parentNode(); 105 | depth--; 106 | if (result == FilterResult.REMOVE) 107 | prev.remove(); // Remove AFTER finding parent. 108 | result = FilterResult.CONTINUE; // Parent was not pruned. 109 | } 110 | // 'tail' current node, then proceed with siblings: 111 | if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) { 112 | result = filter.tail(node, depth); 113 | if (result == FilterResult.STOP) 114 | return result; 115 | } 116 | if (node == root) 117 | return result; 118 | Node prev = node; // In case we need to remove it below. 119 | node = node.nextSibling(); 120 | if (result == FilterResult.REMOVE) 121 | prev.remove(); // Remove AFTER finding sibling. 122 | } 123 | // root == null? 124 | return FilterResult.CONTINUE; 125 | } 126 | 127 | /** 128 | * Start a depth-first filtering of all elements. 129 | * @param filter Node filter. 130 | * @param elements Elements to filter. 131 | */ 132 | public static void filter(NodeFilter filter, Elements elements) { 133 | Validate.notNull(filter); 134 | Validate.notNull(elements); 135 | for (Element el : elements) 136 | if (filter(filter, el) == FilterResult.STOP) 137 | break; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /html/src/org/jsoup/select/NodeVisitor.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.select; 2 | 3 | import org.jsoup.nodes.Node; 4 | 5 | /** 6 | * Node visitor interface. Provide an implementing class to {@link NodeTraversor} to iterate through nodes. 7 | *

8 | * This interface provides two methods, {@code head} and {@code tail}. The head method is called when the node is first 9 | * seen, and the tail method when all of the node's children have been visited. As an example, head can be used to 10 | * create a start tag for a node, and tail to create the end tag. 11 | *

12 | */ 13 | public interface NodeVisitor { 14 | /** 15 | * Callback for when a node is first visited. 16 | * 17 | * @param node the node being visited. 18 | * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node 19 | * of that will have depth 1. 20 | */ 21 | void head(Node node, int depth); 22 | 23 | /** 24 | * Callback for when a node is last visited, after all of its descendants have been visited. 25 | * 26 | * @param node the node being visited. 27 | * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node 28 | * of that will have depth 1. 29 | */ 30 | void tail(Node node, int depth); 31 | } 32 | -------------------------------------------------------------------------------- /html/src/org/jsoup/select/StructuralEvaluator.java: -------------------------------------------------------------------------------- 1 | package org.jsoup.select; 2 | 3 | import org.jsoup.nodes.Element; 4 | 5 | /** 6 | * Base structural evaluator. 7 | */ 8 | abstract class StructuralEvaluator extends Evaluator { 9 | Evaluator evaluator; 10 | 11 | static class Root extends Evaluator { 12 | public boolean matches(Element root, Element element) { 13 | return root == element; 14 | } 15 | } 16 | 17 | static class Has extends StructuralEvaluator { 18 | public Has(Evaluator evaluator) { 19 | this.evaluator = evaluator; 20 | } 21 | 22 | public boolean matches(Element root, Element element) { 23 | for (Element e : element.getAllElements()) { 24 | if (e != element && evaluator.matches(root, e)) 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return String.format(":has(%s)", evaluator); 33 | } 34 | } 35 | 36 | static class Not extends StructuralEvaluator { 37 | public Not(Evaluator evaluator) { 38 | this.evaluator = evaluator; 39 | } 40 | 41 | public boolean matches(Element root, Element node) { 42 | return !evaluator.matches(root, node); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return String.format(":not%s", evaluator); 48 | } 49 | } 50 | 51 | static class Parent extends StructuralEvaluator { 52 | public Parent(Evaluator evaluator) { 53 | this.evaluator = evaluator; 54 | } 55 | 56 | public boolean matches(Element root, Element element) { 57 | if (root == element) 58 | return false; 59 | 60 | Element parent = element.parent(); 61 | while (true) { 62 | if (evaluator.matches(root, parent)) 63 | return true; 64 | if (parent == root) 65 | break; 66 | parent = parent.parent(); 67 | } 68 | return false; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return String.format(":parent%s", evaluator); 74 | } 75 | } 76 | 77 | static class ImmediateParent extends StructuralEvaluator { 78 | public ImmediateParent(Evaluator evaluator) { 79 | this.evaluator = evaluator; 80 | } 81 | 82 | public boolean matches(Element root, Element element) { 83 | if (root == element) 84 | return false; 85 | 86 | Element parent = element.parent(); 87 | return parent != null && evaluator.matches(root, parent); 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return String.format(":ImmediateParent%s", evaluator); 93 | } 94 | } 95 | 96 | static class PreviousSibling extends StructuralEvaluator { 97 | public PreviousSibling(Evaluator evaluator) { 98 | this.evaluator = evaluator; 99 | } 100 | 101 | public boolean matches(Element root, Element element) { 102 | if (root == element) 103 | return false; 104 | 105 | Element prev = element.previousElementSibling(); 106 | 107 | while (prev != null) { 108 | if (evaluator.matches(root, prev)) 109 | return true; 110 | 111 | prev = prev.previousElementSibling(); 112 | } 113 | return false; 114 | } 115 | 116 | @Override 117 | public String toString() { 118 | return String.format(":prev*%s", evaluator); 119 | } 120 | } 121 | 122 | static class ImmediatePreviousSibling extends StructuralEvaluator { 123 | public ImmediatePreviousSibling(Evaluator evaluator) { 124 | this.evaluator = evaluator; 125 | } 126 | 127 | public boolean matches(Element root, Element element) { 128 | if (root == element) 129 | return false; 130 | 131 | Element prev = element.previousElementSibling(); 132 | return prev != null && evaluator.matches(root, prev); 133 | } 134 | 135 | @Override 136 | public String toString() { 137 | return String.format(":prev%s", evaluator); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /html/src/org/jsoup/select/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | Packages to support the CSS-style element selector. 3 | */ 4 | package org.jsoup.select; -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/Callback.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface Callback { 4 | public T run(); 5 | } 6 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/CustomCallback.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface CustomCallback { 4 | public T2 run(T t); 5 | } 6 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/CustomRunnable.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface CustomRunnable { 4 | public void run(T t); 5 | } 6 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/Each.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface Each { 4 | public void run(int index, T t); 5 | } 6 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/GdxCellQuery.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor; 4 | import com.badlogic.gdx.scenes.scene2d.ui.Cell; 5 | /** 6 | * GDX-Query 7 | * more simplified way to enjoy LibGDX 8 | * 9 | * Project website: https://github.com/dingjibang/GDX-Query/ 10 | * RPSG-TEAM: http://www.rpsg-team.com 11 | * 12 | * @author dingjibang 13 | * 14 | */ 15 | public class GdxCellQuery{ 16 | 17 | public static final Test FIRST_CHILD = cq -> cq.query.first().get() == cq.getActor(); 18 | public static final Test LAST_CHILD = cq -> cq.query.last().get() == cq.getActor(); 19 | public static Test INDEX(int i) { 20 | return cell -> cell.query.indexOf(cell.getActor()) == i; 21 | } 22 | 23 | GdxQuery listener; 24 | TQ query; 25 | Cell cell; 26 | 27 | 28 | public static GdxCellQuery build(TQ query, Cell cell){ 29 | GdxCellQuery q = new GdxCellQuery<>(); 30 | q.query = query; 31 | q.cell = cell; 32 | return q; 33 | } 34 | 35 | public GdxCellQuery size(float w, float h) { 36 | cell.size(w, h); 37 | return this; 38 | } 39 | 40 | public GdxCellQuery prefSize(float w, float h) { 41 | cell.prefSize(w, h); 42 | return this; 43 | } 44 | 45 | public GdxCellQuery left(){ 46 | cell.left(); 47 | return this; 48 | } 49 | 50 | public GdxCellQuery right(){ 51 | cell.right(); 52 | return this; 53 | } 54 | 55 | public GdxCellQuery top(){ 56 | cell.top(); 57 | return this; 58 | } 59 | 60 | public GdxCellQuery bottom(){ 61 | cell.bottom(); 62 | return this; 63 | } 64 | 65 | public TQ end(){ 66 | return query; 67 | } 68 | 69 | public GdxCellQuery click(Runnable r){ 70 | listener = new GdxQuery(cell.getActor()).click(r); 71 | return this; 72 | } 73 | 74 | public GdxCellQuery clickIf(Test t){ 75 | if(t.test(this)) 76 | click(); 77 | return this; 78 | } 79 | 80 | public GdxCellQuery click(){ 81 | if(listener != null) 82 | listener.click(); 83 | return this; 84 | } 85 | 86 | public GdxCellQuery bind(Object o){ 87 | cell.getActor().setUserObject(o); 88 | return this; 89 | } 90 | 91 | public Object object(){ 92 | return cell.getActor().getUserObject(); 93 | } 94 | 95 | @SuppressWarnings("unchecked") 96 | public GdxCellQuery cell(T2 a){ 97 | return (GdxCellQuery)end().cell(a); 98 | } 99 | 100 | public GdxCellQuery padLeft(int i) { 101 | cell.padLeft(i); 102 | return this; 103 | } 104 | 105 | public GdxCellQuery padRight(int i) { 106 | cell.padRight(i); 107 | return this; 108 | } 109 | 110 | public GdxCellQuery padTop(int i) { 111 | cell.padTop(i); 112 | return this; 113 | } 114 | 115 | public GdxCellQuery padBottom(int i) { 116 | cell.padBottom(i); 117 | return this; 118 | } 119 | 120 | public GdxCellQuery padLR(int i) { 121 | return padLeft(i).padRight(i); 122 | } 123 | 124 | public GdxCellQuery padTB(int i) { 125 | return padTop(i).padBottom(i); 126 | } 127 | 128 | public GdxCellQuery width(int i) { 129 | cell.width(i); 130 | return this; 131 | } 132 | 133 | public GdxCellQuery height(int i) { 134 | cell.height(i); 135 | return this; 136 | } 137 | 138 | public GdxCellQuery pad(int i) { 139 | return padTB(i).padLR(i); 140 | } 141 | 142 | public GdxCellQuery center() { 143 | cell.center(); 144 | return this; 145 | } 146 | 147 | public T getActor() { 148 | return cell.getActor(); 149 | } 150 | 151 | public GdxCellQuery getActor(CustomRunnable run){ 152 | run.run(getActor()); 153 | return this; 154 | } 155 | 156 | public GdxCellQuery when(Test firstChild, CustomRunnable run) { 157 | if(firstChild.test(this)) 158 | run.run(getActor()); 159 | return this; 160 | } 161 | 162 | public GdxCellQuery row() { 163 | query.row(); 164 | return this; 165 | } 166 | 167 | } -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/GdxFrame.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class GdxFrame { 7 | private Map frames = new HashMap(); 8 | 9 | public GdxFrame add(GdxQuery query,GdxQueryRunnable runnable){ 10 | frames.put(query,runnable); 11 | return this; 12 | } 13 | 14 | public GdxFrame logic(){ 15 | for(GdxQuery query:frames.keySet()) 16 | frames.get(query).run(query); 17 | return this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/GdxQueryRunnable.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface GdxQueryRunnable { 4 | public void run(GdxQuery self); 5 | } 6 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/ListQuery.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ListQuery{ 7 | 8 | private List list = new ArrayList<>(); 9 | 10 | public ListQuery(T1 t1) { 11 | add(t1); 12 | } 13 | 14 | public ListQuery(Class t1) { 15 | 16 | } 17 | 18 | public ListQuery() { 19 | } 20 | 21 | public ListQuery add(T1 t1){ 22 | list.add(t1); 23 | return this; 24 | } 25 | 26 | public List get(){ 27 | return list; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/Map.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface Map{ 4 | public R run(T t); 5 | } 6 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/MapQuery.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | import java.util.HashMap; 4 | 5 | public class MapQuery extends HashMap{ 6 | private static final long serialVersionUID = 1L; 7 | 8 | public MapQuery(T1 t1, T2 t2) { 9 | add(t1,t2); 10 | } 11 | 12 | public MapQuery(Class t1, Class t2) { 13 | 14 | } 15 | 16 | public MapQuery() { 17 | } 18 | 19 | public MapQuery add(T1 t1,T2 t2){ 20 | put(t1,t2); 21 | return this; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/NullActor.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor; 4 | 5 | public class NullActor extends Actor { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/RemoveTest.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface RemoveTest { 4 | public boolean test(T object); 5 | } -------------------------------------------------------------------------------- /html/src/team/rpsg/gdxQuery/Test.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.gdxQuery; 2 | 3 | public interface Test { 4 | public boolean test(GdxCellQuery cq); 5 | } 6 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/HTMLStage.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.OrthographicCamera 5 | import com.badlogic.gdx.scenes.scene2d.Stage 6 | import com.badlogic.gdx.scenes.scene2d.ui.Container 7 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable 8 | import com.badlogic.gdx.utils.Scaling 9 | import com.badlogic.gdx.utils.viewport.ScalingViewport 10 | import com.steadystate.css.dom.CSSRuleListImpl 11 | import com.steadystate.css.dom.CSSStyleDeclarationImpl 12 | import com.steadystate.css.dom.CSSStyleRuleImpl 13 | import com.steadystate.css.dom.CSSUnknownRuleImpl 14 | import com.steadystate.css.parser.CSSOMParser 15 | import com.steadystate.css.parser.SACParserCSS3 16 | import com.sun.webkit.dom.DocumentImpl 17 | import groovy.transform.CompileStatic 18 | import org.jsoup.Jsoup 19 | import org.jsoup.nodes.Document 20 | import org.w3c.css.sac.InputSource 21 | import org.w3c.css.sac.Selector 22 | import org.w3c.dom.css.CSSStyleSheet 23 | import team.rpsg.html.dom.Root 24 | import team.rpsg.html.dom.Dom 25 | import team.rpsg.html.manager.ResourceManager 26 | import team.rpsg.html.util.PathParser 27 | import team.rpsg.html.util.Timer 28 | 29 | import javax.xml.transform.dom.DOMSource 30 | 31 | /** 32 | * GDX-HTML
33 | * using HTMLStage.buildPath(path-to-file) to create your first stage. 34 | */ 35 | 36 | 37 | @CompileStatic 38 | class HTMLStage extends Stage{ 39 | 40 | Timer timer 41 | Document document 42 | Dom rootDom 43 | ResourceManager res 44 | 45 | boolean needsRefresh = false 46 | 47 | List styles = [] 48 | 49 | private HTMLStage(Document document) { 50 | super(new ScalingViewport(Scaling.none, Gdx.graphics.width, Gdx.graphics.height, new OrthographicCamera())) 51 | 52 | this.res = new ResourceManager() 53 | this.document = document 54 | 55 | def base = document.getElementsByTag("base") 56 | if(base.size() != 0){ 57 | this.document.baseUri = base.last().attr("href") 58 | } 59 | 60 | 61 | forceBuild() 62 | } 63 | 64 | void forceBuild(){ 65 | timer = new Timer() 66 | this.clear() 67 | styles.clear() 68 | 69 | document.getElementsByTag("style").each {styles << it.childNode(0).toString()} 70 | document.select("link[rel=\"stylesheet\"]").each { 71 | PathParser.get(document, it.attr("href"), it.attr("charset"), {String s, boolean needsUpdate -> 72 | styles << s 73 | if(needsUpdate) 74 | joinStyles(s) 75 | }) 76 | } 77 | 78 | 79 | joinStyles() 80 | } 81 | 82 | void refresh(boolean isHardRefresh = false){ 83 | if(isHardRefresh){ 84 | forceBuild() 85 | }else{ 86 | clear() 87 | addActor(rootDom = new Root(document.body(), this)) 88 | } 89 | 90 | debugAll = document.getElementsByTag("html").attr("debug").equalsIgnoreCase("true") 91 | } 92 | 93 | 94 | void joinStyles(String style = null){ 95 | InputSource source = new InputSource(new StringReader(style ?: styles.join("\n"))) 96 | CSSOMParser parser = new CSSOMParser(new SACParserCSS3()) 97 | CSSStyleSheet styleSheet = parser.parseStyleSheet(source, null, null) 98 | 99 | CSSRuleListImpl rules = styleSheet.getCssRules() as CSSRuleListImpl 100 | for(def i = 0; i < rules.getLength(); i++){ 101 | if(!(rules.item(i) instanceof CSSStyleRuleImpl)) 102 | continue 103 | 104 | CSSStyleRuleImpl item = rules.item(i) as CSSStyleRuleImpl 105 | 106 | for(def j = 0; j < item.selectors.getLength(); j++) { 107 | Selector selector = item.selectors.item(j) 108 | 109 | def s = selector.toString().trim() 110 | 111 | if(s.indexOf(':') >= 0) 112 | continue 113 | 114 | if(s.length() == 0) 115 | continue 116 | 117 | //println "s: " + s + ", default: " + selector.toString() 118 | 119 | try{ 120 | document.select(s)*.styles*.add ((item.style as CSSStyleDeclarationImpl).properties) 121 | }catch(e){ 122 | e.printStackTrace() 123 | } 124 | 125 | } 126 | } 127 | 128 | needsRefresh = true 129 | } 130 | 131 | void draw() { 132 | if(needsRefresh){ 133 | needsRefresh = false 134 | refresh() 135 | } 136 | super.draw() 137 | } 138 | 139 | void act() { 140 | timer.act() 141 | res.act() 142 | super.act() 143 | } 144 | 145 | static HTMLStage buildHTML(String html, String uriPath = null){ 146 | def doc = Jsoup.parse(html) 147 | 148 | if(uriPath) 149 | doc.baseUri = uriPath 150 | 151 | buildDocument doc 152 | } 153 | 154 | static HTMLStage buildDocument(Document document){ 155 | new HTMLStage(document) 156 | } 157 | 158 | static HTMLStage buildPath(String path){ 159 | buildHTML(Gdx.files.internal(path).readString("utf-8"), path) 160 | } 161 | 162 | void dispose() { 163 | document = null 164 | rootDom = null 165 | res.dispose() 166 | super.dispose() 167 | } 168 | 169 | void resize(int width, int height){ 170 | viewport.setWorldSize(width, height) 171 | viewport.update(width, height, true) 172 | 173 | refresh(false) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/HTMLDom.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom 2 | 3 | import com.steadystate.css.dom.Property 4 | import groovy.transform.CompileStatic 5 | import org.jsoup.nodes.Node 6 | 7 | @CompileStatic 8 | class HTMLDom extends Dom { 9 | private String tagName 10 | 11 | HTMLDom(Node node, List styles, String tagName){ 12 | super(node) 13 | this.node.defaultStyles = styles 14 | this.tagName = tagName 15 | } 16 | 17 | String toString() { 18 | return "HTMLDom(${tagName}) ${super.toString()}" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/HTMLDomPreset.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom 2 | 3 | import com.steadystate.css.dom.CSSStyleDeclarationImpl 4 | import com.steadystate.css.dom.Property 5 | import com.steadystate.css.parser.CSSOMParser 6 | import com.steadystate.css.parser.SACParserCSS3 7 | import groovy.transform.CompileStatic 8 | import org.w3c.css.sac.InputSource 9 | import team.rpsg.html.util.StyleParser 10 | 11 | @CompileStatic 12 | enum HTMLDomPreset { 13 | ADDRESS("display: block;font-style: italic;"), 14 | AREA("display: none;"), 15 | ARTICLE("display: block; "), 16 | ASIDE("display: block; "), 17 | B("font-weight: bold;"), 18 | BDO("unicode-bidi: bidi-override;"), 19 | BLOCKQUOTE("display: block;margin-top: 1em;margin-bottom: 1em;margin-left: 40px;margin-right: 40px;"), 20 | BODY("display: block;margin: 8px;"), 21 | BR("display: block;"), 22 | CAPTION("display: table-caption;text-align: center;"), 23 | CITE("font-style: italic;"), 24 | CODE("font-family: monospace;"), 25 | COL("display: table-column;"), 26 | COLGROUP("display: table-column-group"), 27 | DATALIST("display: none;"), 28 | DD("display: block;margin-left: 40px;"), 29 | DEL("text-decoration: line-through;"), 30 | DETAILS("display: block;"), 31 | DFN("font-style: italic;"), 32 | DIV("display: block;"), 33 | DL("display: block;margin-top: 1em;margin-bottom: 1em;margin-left: 0;margin-right: 0;"), 34 | DT("display: block;"), 35 | EM("font-style: italic;"), 36 | FIELDSET("display: block;margin-left: 2px;margin-right: 2px;padding-top: 0.35em;padding-bottom: 0.625em;padding-left: 0.75em;padding-right: 0.75em;border: 2px groove (internal value);"), 37 | FIGCAPTION("display: block;"), 38 | FIGURE("display: block;margin-top: 1em;margin-bottom: 1em;margin-left: 40px;margin-right: 40px;"), 39 | FOOTER("display: block;"), 40 | FORM("display: block;margin-top: 0em;"), 41 | H1("display: block;font-size: 2em;margin-top: 0.67em;margin-bottom: 0.67em;margin-left: 0;margin-right: 0;font-weight: bold;"), 42 | H2("display: block;font-size: 1.5em;margin-top: 0.83em;margin-bottom: 0.83em;margin-left: 0;margin-right: 0;font-weight: bold;"), 43 | H3("display: block;font-size: 1.17em;margin-top: 1em;margin-bottom: 1em;margin-left: 0;margin-right: 0;font-weight: bold;"), 44 | H4("display: block;margin-top: 1.33em;margin-bottom: 1.33em;margin-left: 0;margin-right: 0;font-weight: bold;"), 45 | H5("display: block;font-size: .83em;margin-top: 1.67em;margin-bottom: 1.67em;margin-left: 0;margin-right: 0;font-weight: bold;"), 46 | H6("display: block;font-size: .67em;margin-top: 2.33em;margin-bottom: 2.33em;margin-left: 0;margin-right: 0;font-weight: bold;"), 47 | HEAD("display: none;"), 48 | HEADER("display: block;"), 49 | HR("display: block;margin-top: 0.5em;margin-bottom: 0.5em;margin-left: auto;margin-right: auto;border-style: inset;border-width: 1px;"), 50 | HTML("display: block;"), 51 | I("font-style: italic;"), 52 | IMG("display: inline-block;"), 53 | IFRAME("display: block;"), 54 | INS("text-decoration: underline;"), 55 | KBD("font-family: monospace;"), 56 | LABEL("cursor: default;"), 57 | LEGEND("display: block;padding-left: 2px;padding-right: 2px;border: none;"), 58 | LI("display: list-item;"), 59 | LINK("display: none;"), 60 | MAP("display: inline;"), 61 | MARK("background-color: yellow;color: black;"), 62 | MENU("display: block;list-style-type: disc;margin-top: 1em;margin-bottom: 1em;margin-left: 0;margin-right: 0;padding-left: 40px;"), 63 | NAV("display: block;"), 64 | OL("display: block;list-style-type: decimal;margin-top: 1em;margin-bottom: 1em;margin-left: 0;margin-right: 0;padding-left: 40px;"), 65 | OUTPUT("display: inline;"), 66 | P("display: block;margin-top: 1em;margin-bottom: 1em;margin-left: 0;margin-right: 0;"), 67 | PARAM("display: none;"), 68 | PRE("display: block;font-family: monospace;white-space: pre;margin: 1em 0;"), 69 | Q("display: inline;"), 70 | RT("line-height: normal;"), 71 | S("text-decoration: line-through;"), 72 | SAMP("font-family: monospace;"), 73 | SCRIPT("display: none;"), 74 | SECTION("display: block;"), 75 | SMALL("font-size: smaller;"), 76 | STRIKE("text-decoration: line-through;"), 77 | STRONG("font-weight: bold;"), 78 | STYLE("display: none;"), 79 | SUB("vertical-align: sub;font-size: smaller;"), 80 | SUMMARY("display: block;"), 81 | SUP("vertical-align: super;font-size: smaller;"), 82 | TABLE("display: table;border-collapse: separate;border-spacing: 2px;border-color: gray;"), 83 | TBODY("display: table-row-group;vertical-align: middle;border-color: inherit;"), 84 | TD("display: table-cell;vertical-align: inherit;"), 85 | TFOOT("display: table-footer-group;vertical-align: middle;border-color: inherit;"), 86 | TH("display: table-cell;vertical-align: inherit;font-weight: bold;text-align: center;"), 87 | THEAD("display: table-header-group;vertical-align: middle;border-color: inherit;"), 88 | TITLE("display: none;"), 89 | TR("display: table-row;vertical-align: inherit;border-color: inherit;"), 90 | U("text-decoration: underline;"), 91 | UL("display: block;list-style-type: disc;margin-top: 1em;margin-bottom: 1 em;margin-left: 0;margin-right: 0;padding-left: 40px;"), 92 | VAR("font-style: italic;") 93 | 94 | private List styles 95 | 96 | private HTMLDomPreset(String stylesString){ 97 | if(stylesString.length() == 0){ 98 | styles = new ArrayList<>() 99 | return 100 | } 101 | 102 | styles = StyleParser.parse(stylesString).properties 103 | } 104 | 105 | HTMLDom dom(org.jsoup.nodes.Node node){ 106 | return new HTMLDom(node, styles, name()) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/Root.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom 2 | 3 | import com.badlogic.gdx.graphics.g2d.Batch 4 | import com.badlogic.gdx.utils.Align 5 | import groovy.transform.CompileStatic 6 | import org.jsoup.nodes.Node 7 | import team.rpsg.html.HTMLStage 8 | 9 | /** 10 | * rootDom = html document 11 | */ 12 | @CompileStatic 13 | class Root extends Dom{ 14 | 15 | Root(Node node, HTMLStage stage){ 16 | super(node) 17 | 18 | this.stage = stage 19 | 20 | parse() 21 | 22 | width = stage.width 23 | height = stage.height 24 | 25 | build() 26 | 27 | x = 0 28 | y = 0 29 | 30 | buildChild() 31 | 32 | align(Align.topLeft) 33 | } 34 | 35 | void draw(Batch batch, float parentAlpha) { 36 | backgroundDrawable?.draw(batch, x, y, width, height) 37 | super.draw(batch, parentAlpha) 38 | } 39 | 40 | void dispose() { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/UnknownDom.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom 2 | 3 | import groovy.transform.CompileStatic 4 | import org.jsoup.nodes.Node 5 | @CompileStatic 6 | class UnknownDom extends Dom{ 7 | 8 | UnknownDom(Node node){ 9 | super(node) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/layout/AbstractLayout.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom.layout 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import groovy.transform.CompileStatic 5 | import team.rpsg.html.dom.Dom 6 | 7 | @CompileStatic 8 | abstract class AbstractLayout { 9 | Dom dom 10 | 11 | AbstractLayout(Dom dom){ 12 | this.dom = dom 13 | } 14 | 15 | abstract void parse(); 16 | abstract Actor build(); 17 | 18 | void addQuirks(Dom dom){ 19 | 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/layout/InlineLayout.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom.layout 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import team.rpsg.html.dom.Dom 5 | 6 | class InlineLayout extends AbstractLayout { 7 | InlineLayout(Dom dom) { 8 | super(dom) 9 | } 10 | 11 | void parse() { 12 | 13 | } 14 | 15 | Actor build() { 16 | return null 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/layout/TableLayout.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom.layout 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.scenes.scene2d.ui.Cell 5 | import com.badlogic.gdx.scenes.scene2d.ui.Container 6 | import com.badlogic.gdx.scenes.scene2d.ui.Table 7 | import com.badlogic.gdx.scenes.scene2d.ui.Value 8 | import com.badlogic.gdx.utils.GdxRuntimeException 9 | import org.jsoup.nodes.Element 10 | import team.rpsg.html.dom.Dom 11 | import team.rpsg.html.util.AlignParser 12 | import team.rpsg.html.util.SizeParser 13 | 14 | class TableLayout extends AbstractLayout{ 15 | static String[] DISPLAY_NAME = ["table", "table-row", "table-cell", "table-row-group", "table-header-group"] 16 | 17 | TableLayout parent 18 | 19 | boolean isTable, isTR, isTD, isTHead, isTBody 20 | 21 | 22 | Table table 23 | Cell current 24 | 25 | TableLayout(Dom dom) { 26 | super(dom) 27 | if(!(dom.node instanceof Element)) 28 | throw new GdxRuntimeException("must case **DOM ELEMENT** to table.") 29 | } 30 | 31 | void parse(){ 32 | if(dom.parentDom.layout instanceof TableLayout) 33 | parent = dom.parentDom.layout as TableLayout 34 | 35 | def display = dom.display 36 | 37 | switch (display.toLowerCase()){ 38 | case "table": 39 | isTable = true 40 | break 41 | case "table-row": 42 | isTR = true 43 | break 44 | case "table-row-group": 45 | isTBody = true 46 | break 47 | case "table-header-group": 48 | isTHead = true 49 | break 50 | case "table-cell": 51 | isTD = true 52 | break 53 | } 54 | } 55 | 56 | Actor build() { 57 | table = parent?.table 58 | 59 | if(!isTable && table == null) 60 | return dom 61 | 62 | if(isTable){ 63 | 64 | dom.parse() 65 | 66 | table = new Table().left().top() 67 | table.fillParent = true 68 | 69 | dom.debugStrings.add { "table:${Math.random()}" + table.width + ", " + table.height } 70 | 71 | def container = new Container(table) 72 | container.width(new Value() { 73 | float get(Actor context) { 74 | dom.widthValue != null ? dom.width : Math.max(context.width, (context as Table).prefWidth) 75 | } 76 | }) 77 | container.height(new Value() { 78 | float get(Actor context) { 79 | dom.heightValue != null ? dom.height : Math.max(context.height, (context as Table).prefHeight) 80 | } 81 | }) 82 | dom.addActor(container) 83 | 84 | return dom 85 | 86 | } else if(isTR || isTBody || isTHead){ 87 | table.row() 88 | return null 89 | } else if(isTD){ 90 | dom.parse() 91 | def c = add(dom) 92 | dom.debugStrings.add { "cell: pw" + c.getPrefWidth() + ", ph" + c.getPrefHeight() } 93 | 94 | return null 95 | } 96 | 97 | return dom 98 | } 99 | 100 | static def first = true 101 | 102 | private void setCellWidth(){ 103 | current.width(new Value() { 104 | float get(Actor context) { 105 | Math.max((context as Dom).prefWidth, context.width) 106 | } 107 | }) 108 | } 109 | 110 | private void setCellHeight(){ 111 | current.height(new Value() { 112 | float get(Actor context) { 113 | Math.max((context as Dom).prefHeight, context.height) 114 | } 115 | }) 116 | } 117 | 118 | 119 | Cell add(Dom dom){ 120 | current = table.add(dom).align(AlignParser.join(dom.textAlign, dom.verticalAlign)) 121 | 122 | if(dom.widthValue){ 123 | if(dom.widthValue instanceof Value.Fixed){ 124 | setCellWidth() 125 | } 126 | 127 | if(dom.widthValue instanceof SizeParser.percentInnerValue){ 128 | def percent = (dom.widthValue as SizeParser.percentInnerValue).percent 129 | current.width(new Value() { 130 | float get(Actor context) { 131 | parent.table.width * percent 132 | } 133 | }) 134 | } 135 | }else { 136 | setCellWidth() 137 | current.expandX() 138 | } 139 | if(dom.heightValue){ 140 | if(dom.heightValue instanceof Value.Fixed){ 141 | setCellHeight() 142 | } 143 | if(dom.heightValue instanceof SizeParser.percentInnerValue){ 144 | def percent = (dom.heightValue as SizeParser.percentInnerValue).percent 145 | current.height(new Value() { 146 | float get(Actor context) { 147 | parent.table.height * percent 148 | } 149 | }) 150 | } 151 | }else{ 152 | setCellHeight() 153 | current.expandY() 154 | } 155 | } 156 | 157 | void addQuirks(Dom dom) { 158 | 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/node/Image.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom.node 2 | 3 | import com.badlogic.gdx.graphics.g2d.Batch 4 | import com.badlogic.gdx.scenes.scene2d.Actor 5 | import com.badlogic.gdx.scenes.scene2d.ui.Container 6 | import com.badlogic.gdx.scenes.scene2d.ui.Value 7 | import com.badlogic.gdx.utils.Scaling 8 | import groovy.transform.CompileStatic 9 | import org.jsoup.nodes.Node 10 | import team.rpsg.html.HTMLStage 11 | import team.rpsg.html.dom.HTMLDom 12 | import team.rpsg.html.manager.widget.AsyncLoadImage 13 | import team.rpsg.html.util.PathParser 14 | import team.rpsg.html.util.StyleParser 15 | 16 | /** 17 | * HTML Image Node.
18 | * It uses lazy loading to load images asynchronously. 19 | *
20 | *
21 | * HTML的图片实现
22 | * 它是异步加载的哦。 23 | */ 24 | @CompileStatic 25 | class Image extends HTMLDom { 26 | 27 | float size 28 | 29 | private static final String DEFAULT_STYLE = "display: inline-block;" 30 | String src = "" 31 | Scaling scaling 32 | 33 | team.rpsg.html.manager.widget.Image img 34 | 35 | boolean async = true 36 | 37 | 38 | Image(Node node) { 39 | super(node, StyleParser.parse(DEFAULT_STYLE).properties, "img") 40 | } 41 | 42 | void parse() { 43 | super.parse() 44 | 45 | src = node.attr "src" 46 | if(src){ 47 | src = PathParser.parse((stage as HTMLStage).document, src) 48 | } 49 | 50 | scaling = style("-gdx-image-scaling", null, {str -> 51 | if(!str) 52 | return null 53 | return Scaling.valueOf(str.toString()) 54 | }) as Scaling 55 | 56 | if(node.attr("async")) 57 | async = node.attr("async").equalsIgnoreCase("true") 58 | } 59 | 60 | void build() { 61 | super.build() 62 | if(src && src.length() != 0){ 63 | 64 | if(async){ 65 | boolean needH = false 66 | boolean needW = false 67 | 68 | Container container = new Container<>() 69 | 70 | def callback = {AsyncLoadImage img -> 71 | img.layout() 72 | 73 | if(needH){ 74 | container.height(new Value() { 75 | float get(Actor context) { 76 | img.imageHeight 77 | } 78 | }) 79 | } 80 | 81 | if(needW){ 82 | container.width(new Value() { 83 | float get(Actor context) { 84 | img.imageWidth 85 | } 86 | }) 87 | } 88 | 89 | } 90 | 91 | img = new AsyncLoadImage(src, callback, res) 92 | container = new Container(img) 93 | 94 | 95 | def setScaling = true 96 | 97 | if(width != 0 && height == 0){ 98 | container.width(img.width = width) 99 | needH = true 100 | }else if(height != 0 && width == 0){ 101 | container.height(img.height = height) 102 | needW = true 103 | }else if(width != 0 && height != 0){ 104 | container.width(img.width = width) 105 | container.height(img.height = height) 106 | img.scaling = Scaling.stretch 107 | setScaling = false 108 | } 109 | 110 | if(setScaling || (!setScaling && scaling != null)) 111 | img.scaling = scaling ?: Scaling.fit 112 | 113 | 114 | 115 | current.addActor container 116 | }else{ 117 | img = new team.rpsg.html.manager.widget.Image(res.getTexture(src)) 118 | if(width != 0 && height != 0){ 119 | img.width = width 120 | img.height = height 121 | } else if(width != 0 && height == 0){ 122 | img.width = width 123 | } else if(height != 0 && width == 0){ 124 | img.height = height 125 | } 126 | 127 | img.scaling = scaling ?: Scaling.fit 128 | img.layout() 129 | 130 | width = img.imageWidth 131 | height = img.imageHeight 132 | 133 | 134 | current.addActor new Container(img) 135 | .width(new Value.Fixed(img.imageWidth)) 136 | .height(new Value.Fixed(img.imageHeight)) 137 | 138 | } 139 | 140 | } 141 | } 142 | 143 | 144 | void draw(Batch batch, float parentAlpha) { 145 | super.draw(batch, parentAlpha) 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/dom/node/Text.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.dom.node 2 | 3 | import com.badlogic.gdx.graphics.Color 4 | import com.badlogic.gdx.scenes.scene2d.ui.Container 5 | import com.badlogic.gdx.utils.Align 6 | import groovy.transform.CompileStatic 7 | import org.jsoup.nodes.Node 8 | import org.jsoup.nodes.TextNode 9 | import team.rpsg.html.dom.Dom 10 | import team.rpsg.html.manager.widget.Label 11 | import team.rpsg.html.util.AlignParser 12 | import team.rpsg.html.util.ColorParser 13 | import team.rpsg.html.util.SizeParser 14 | 15 | /** 16 | * HTML Text Node.
17 | * It is implemented by the label of LibGDX.
18 | * It defaults to not enabling wrap and can be enabled with "-gdx-wrap: true;"
19 | * To enabling wrap requires the parent container to set the width, but if the container contains multiple child elements, it will cause a width bug. 20 | * If you need to use multiple colored child elements in the wrap-enabled container, it is recommended to use "-gdx-markup: true; " 21 | *
22 | *
23 | * HTML文字Node
24 | * 它使用gdx的Label组件实现的,默认情况下它不会开启换行,你可以在它的Dom设置"-gdx-wrap: true;"
25 | * 但是,开启换行会导致一个宽度的bug(所有子元素宽度一致),目前没有办法解决除非重写一个文档流。
26 | * 所以如果想启用换行,还想让该段文字是多彩的,我们推荐你使用"-gdx-markup: true;",启用gdx的markup颜色语言,而不是使用一堆子元素。 27 | */ 28 | @CompileStatic 29 | class Text extends Dom { 30 | 31 | int fontSize = 30 32 | def text = "" 33 | Color textColor = Color.WHITE 34 | Float lineHeight = Label.AUTO_LINE_HEIGHT 35 | Integer letterSpacing = Label.AUTO_LETTER_SPACING 36 | 37 | private String wrap 38 | private boolean markup 39 | 40 | 41 | Text(Node node) { 42 | super(node) 43 | } 44 | 45 | void parse() { 46 | super.parse() 47 | 48 | text = (node as TextNode).text() 49 | textColor = style("color", "white", ColorParser.&parse) as Color 50 | fontSize = style("font-size", "16px", SizeParser.&parseFontPX) as int 51 | 52 | wrap = style("-gdx-wrap", "false", {p -> p?.toString()}) 53 | markup = style("-gdx-markup", "false", {p -> p?.toString()?.toBoolean()}) 54 | 55 | lineHeight = style("line-height", null, {v -> v ? SizeParser.parse(v)?.get(parentDom)?.toFloat() : Label.AUTO_LINE_HEIGHT}) as Float 56 | letterSpacing = style("letter-spacing", null, {v -> v ? SizeParser.parse(v)?.get(parentDom)?.toInteger() : Label.AUTO_LETTER_SPACING}) as Integer 57 | } 58 | 59 | void build() { 60 | super.build() 61 | def label = res.text.getLabel(text, fontSize) 62 | label.color = textColor ?: Color.WHITE 63 | label.lineHeight = lineHeight 64 | label.letterSpacing = letterSpacing 65 | 66 | label.alignment = Align.left 67 | 68 | if(markup) 69 | label.style.font.data.markupEnabled = markup 70 | 71 | if(wrap && wrap == "true"){ 72 | label.wrap = true 73 | 74 | def container = new Container(label).align(textAlign) 75 | container.width(parentDom.width) 76 | current.addActor container 77 | }else { 78 | current.addActor label 79 | } 80 | 81 | 82 | 83 | 84 | 85 | } 86 | 87 | void dispose() { 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/manager/ResourceManager.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.manager 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.assets.AssetLoaderParameters 5 | import com.badlogic.gdx.assets.AssetManager 6 | import com.badlogic.gdx.graphics.Color 7 | import com.badlogic.gdx.graphics.Pixmap 8 | import com.badlogic.gdx.graphics.Texture 9 | import com.badlogic.gdx.graphics.g2d.Sprite 10 | import com.badlogic.gdx.graphics.g2d.TextureRegion 11 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable 12 | import com.badlogic.gdx.scenes.scene2d.utils.SpriteDrawable 13 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable 14 | import groovy.transform.CompileStatic 15 | import team.rpsg.html.manager.widget.AsyncLoadImage 16 | import team.rpsg.html.manager.widget.Image 17 | 18 | @CompileStatic 19 | class ResourceManager { 20 | 21 | /**资源管理线程*/ 22 | public AssetManager assetManager 23 | /**字体管理*/ 24 | public TextManager text 25 | 26 | private SpriteDrawable defaultSpriteDrawable 27 | 28 | /**初始化*/ 29 | ResourceManager(){ 30 | assetManager = new AssetManager() 31 | /**添加字体管理器*/ 32 | text = new TextManager() 33 | 34 | def pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888) 35 | pixmap.drawPixel(0, 0, Color.rgba8888(Color.WHITE)) 36 | 37 | def tex = new Texture(pixmap) 38 | pixmap.dispose() 39 | 40 | defaultSpriteDrawable = new SpriteDrawable(new Sprite(tex)) 41 | } 42 | 43 | /** 44 | * 获取一张图片,它是异步加载的,如果需要同步加载,请调用{@link #sync(String) sync}方法 45 | */ 46 | AsyncLoadImage get(String path){ 47 | new AsyncLoadImage(path, this) 48 | } 49 | 50 | /** 51 | * 立即获取一张图片 52 | */ 53 | Image sync(String path){ 54 | return new Image(getTexture(path)) 55 | } 56 | 57 | /** 58 | * 立即获取一张drawable 59 | */ 60 | SpriteDrawable getDrawable(String path) { 61 | new SpriteDrawable(new Sprite(getTexture(path))) 62 | } 63 | 64 | SpriteDrawable defaultDrawable(Color color = Color.WHITE){ 65 | defaultSpriteDrawable.tint(color) 66 | } 67 | 68 | /** 69 | * 更新资源管理器 70 | */ 71 | boolean act(){ 72 | return assetManager.update() 73 | } 74 | 75 | /** 76 | * 立即获取一张纹理 77 | */ 78 | Texture getTexture(String path){ 79 | assetManager.load(path, Texture.class) 80 | while(!assetManager.update()){} 81 | return assetManager.get(path, Texture.class) 82 | } 83 | 84 | /** 85 | * 预加载纹理 86 | */ 87 | void preInit(String[] paths){ 88 | for(String path : paths) 89 | assetManager.load(path, Texture.class) 90 | } 91 | 92 | /**卸载全部纹理*/ 93 | void dispose(){ 94 | assetManager.dispose() 95 | text.dispose() 96 | defaultSpriteDrawable.sprite.texture.dispose() 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/manager/TextManager.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.manager 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.g2d.BitmapFont 5 | import com.badlogic.gdx.graphics.g2d.GlyphLayout 6 | import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator 7 | import com.badlogic.gdx.utils.Pools 8 | import groovy.transform.CompileStatic 9 | import team.rpsg.html.manager.widget.Label 10 | import team.rpsg.lazyFont.LazyBitmapFont 11 | 12 | @CompileStatic 13 | class TextManager { 14 | private Map map = new HashMap<>() 15 | 16 | FreeTypeFontGenerator NORMAL_GENERATOR = new FreeTypeFontGenerator(Gdx.files.internal("font/xyj.ttf")) 17 | FreeTypeFontGenerator ENGLISH_GENERATOR = new FreeTypeFontGenerator(Gdx.files.internal("font/Coold.ttf")) 18 | 19 | 20 | LazyBitmapFont get(int fontSize, FreeTypeFontGenerator gen) { 21 | LazyBitmapFont font = null 22 | 23 | for(Param k : map.keySet()) 24 | if(k.size == fontSize && k.gen == gen) 25 | font = map.get(k) 26 | 27 | if (font == null) { 28 | font = gen == null ? new LazyBitmapFont(NORMAL_GENERATOR, fontSize) : new LazyBitmapFont(gen, fontSize) 29 | map.put(new Param(fontSize, gen), font) 30 | } 31 | 32 | return font 33 | } 34 | 35 | LazyBitmapFont get(int fontSize) { 36 | return get(fontSize, null) 37 | } 38 | 39 | Label getLabel(int fontSize) { 40 | return getLabel("", fontSize) 41 | } 42 | 43 | Label getLabel(Object text, int fontSize) { 44 | return getLabel(text, fontSize, null) 45 | } 46 | 47 | Label getLabel(Object text, int fontSize, FreeTypeFontGenerator gen) { 48 | com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle ls = new com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle() 49 | ls.font = gen == null ? get(fontSize) : get(fontSize, gen) 50 | 51 | return new Label(text.toString(), ls) 52 | } 53 | 54 | 55 | private static class Param{ 56 | int size 57 | FreeTypeFontGenerator gen 58 | 59 | Param(int size, FreeTypeFontGenerator gen) { 60 | this.size = size 61 | this.gen = gen 62 | } 63 | 64 | } 65 | 66 | int getTextWidth(String str, int size) { 67 | return getTextWidth(get(size),str) 68 | 69 | } 70 | 71 | static int getTextWidth(BitmapFont font, String str){ 72 | GlyphLayout layout = Pools.obtain(GlyphLayout.class) 73 | layout.setText(font, str) 74 | return (int) layout.width 75 | } 76 | 77 | void dispose(){ 78 | NORMAL_GENERATOR.dispose() 79 | ENGLISH_GENERATOR.dispose() 80 | 81 | map.each {k, v -> v.dispose()} 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/manager/widget/AsyncLoadImage.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.manager.widget; 2 | 3 | import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter; 4 | import com.badlogic.gdx.graphics.Texture; 5 | import com.badlogic.gdx.graphics.Texture.TextureFilter; 6 | import com.badlogic.gdx.graphics.g2d.Batch; 7 | import com.badlogic.gdx.graphics.g2d.TextureRegion; 8 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; 9 | import team.rpsg.gdxQuery.CustomRunnable; 10 | import team.rpsg.html.manager.ResourceManager; 11 | 12 | /** 13 | * GDX-RPG 异步加载纹理类
14 | * 启用另一个OpenGL线程来达到异步加载纹理的效果,在加载的过程中,当前的图片是无法读取宽/高度的。 15 | */ 16 | public class AsyncLoadImage extends Image { 17 | 18 | private boolean loaded = false, loading = false; 19 | private int originAlignment; 20 | private String texturePath; 21 | 22 | private CustomRunnable _onLoaded = null; 23 | 24 | private ResourceManager resourceManager = null; 25 | 26 | 27 | private AsyncLoadImage() {} 28 | 29 | public AsyncLoadImage(String texturePath, ResourceManager resourceManager) { 30 | this.texturePath = texturePath; 31 | this.resourceManager = resourceManager; 32 | 33 | loadTexture(); 34 | } 35 | 36 | public AsyncLoadImage(String texturePath, CustomRunnable onLoaded, ResourceManager resourceManager) { 37 | this(texturePath, resourceManager); 38 | this._onLoaded = onLoaded; 39 | 40 | loadTexture(); 41 | } 42 | 43 | /** 44 | * 初始化 45 | */ 46 | private void init(){ 47 | /**如果开启纹理过滤,则设置*/ 48 | ((TextureRegionDrawable)getDrawable()).getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear); 49 | 50 | /**重置自身大小*/ 51 | if (getWidth() == 0) 52 | setWidth(getDrawable().getMinWidth()); 53 | if (getHeight() == 0) 54 | setHeight(getDrawable().getMinHeight()); 55 | if (originAlignment != -1) 56 | setOrigin(originAlignment); 57 | 58 | } 59 | 60 | /**当该纹理被调用绘制时,才开始懒加载纹理,否则歇着*/ 61 | public void draw(Batch batch, float parentAlpha) { 62 | super.draw(batch, parentAlpha); 63 | } 64 | 65 | 66 | TextureParameter parameter = new TextureParameter(); 67 | { 68 | /**当纹理加载完成后的回调*/ 69 | parameter.loadedCallback = (assetManager, fileName, type) -> { 70 | loaded = true; 71 | 72 | setDrawable(new TextureRegionDrawable(new TextureRegion(resourceManager.assetManager.get(texturePath, Texture.class)))); 73 | init(); 74 | 75 | if(_onLoaded != null) 76 | _onLoaded.run(AsyncLoadImage.this); 77 | }; 78 | } 79 | 80 | /**加载纹理**/ 81 | public void loadTexture(){ 82 | if(texturePath == null || loading) 83 | return; 84 | 85 | loading = true; 86 | 87 | resourceManager.assetManager.load(texturePath, Texture.class, parameter); 88 | } 89 | 90 | /**是否加载完成*/ 91 | public boolean loaded(){ 92 | return loaded; 93 | } 94 | 95 | public void setOrigin(int alignment) { 96 | super.setOrigin(originAlignment = alignment); 97 | } 98 | 99 | /** 100 | * 异步加载一个drawable 101 | */ 102 | public void setDrawableAsync(String drawablePath) { 103 | new AsyncLoadImage(drawablePath, that -> { 104 | setDrawable(that.getDrawable()); 105 | init(); 106 | }, resourceManager).loadTexture(); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/manager/widget/AutoSizeContainer.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.manager.widget; 2 | 3 | import com.badlogic.gdx.scenes.scene2d.ui.Container; 4 | import team.rpsg.html.dom.Dom; 5 | 6 | public class AutoSizeContainer extends Container { 7 | 8 | public AutoSizeContainer(Dom dom){ 9 | super(dom); 10 | } 11 | 12 | public float getWidth() { 13 | return getPrefWidth(); 14 | } 15 | 16 | public float getHeight() { 17 | return getPrefHeight(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/manager/widget/Image.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.manager.widget; 2 | 3 | import com.badlogic.gdx.graphics.Texture; 4 | import com.badlogic.gdx.graphics.Texture.TextureFilter; 5 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 6 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; 7 | import team.rpsg.gdxQuery.TypedGdxQuery; 8 | 9 | /** 10 | * GDX-RPG 图片类 11 | */ 12 | public class Image extends com.badlogic.gdx.scenes.scene2d.ui.Image{ 13 | public TypedGdxQuery query(){ 14 | return new TypedGdxQuery<>(this); 15 | } 16 | 17 | public Image (Texture texture){ 18 | super(texture); 19 | texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); 20 | } 21 | 22 | public void setDrawable(Drawable drawable) { 23 | super.setDrawable(drawable); 24 | 25 | if(drawable == null) 26 | return; 27 | 28 | if(drawable instanceof TextureRegionDrawable) 29 | ((TextureRegionDrawable)drawable).getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear); 30 | } 31 | 32 | public Image (Drawable drawable){ 33 | super(drawable); 34 | } 35 | 36 | public Image(){ 37 | super((Drawable)null); 38 | } 39 | 40 | /**使用整数坐标,以免导致纹理失真*/ 41 | public void setWidth(float width) { 42 | super.setWidth((int)width); 43 | } 44 | 45 | public void setHeight(float height) { 46 | super.setHeight((int)height); 47 | } 48 | 49 | public void setX(float x) { 50 | super.setX((int)x); 51 | } 52 | 53 | public void setY(float y) { 54 | super.setY((int)y); 55 | } 56 | 57 | public void setPosition(float x, float y) { 58 | super.setPosition((int)x, (int)y); 59 | } 60 | 61 | public boolean isTransparent() { 62 | return getColor().a <= 0; 63 | } 64 | 65 | public Texture getTexture() { 66 | if(getDrawable() instanceof TextureRegionDrawable) 67 | return ((TextureRegionDrawable) getDrawable()).getRegion().getTexture(); 68 | 69 | return null; 70 | } 71 | 72 | public Image disableTouch(){ 73 | setTouchable(null); 74 | return this; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/util/AlignParser.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.util 2 | 3 | import com.badlogic.gdx.utils.Align 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | class AlignParser { 8 | static int textAlign(Object align, int defaultAlign = Align.left){ 9 | if(!align || align.toString().length() == 0) 10 | return defaultAlign 11 | 12 | def s = align.toString() 13 | 14 | switch (s.toLowerCase()){ 15 | case "left": return Align.left 16 | case "right": return Align.right 17 | case "center": return Align.center 18 | } 19 | 20 | return defaultAlign 21 | } 22 | 23 | static int verticalAlign(Object align, int defaultAlign = Align.bottom){ 24 | 25 | if(!align || align.toString().length() == 0) 26 | return defaultAlign 27 | 28 | def s = align.toString() 29 | 30 | switch (s.toLowerCase()){ 31 | case "top": return Align.top 32 | case "middle": return Align.center 33 | case "center": return Align.center 34 | case "bottom": return Align.bottom 35 | } 36 | 37 | return defaultAlign 38 | } 39 | 40 | static int join(int lr, int tb){ 41 | if(lr == Align.center && tb == Align.center) 42 | return Align.center 43 | if(lr == Align.center) 44 | return tb 45 | if(tb == Align.center) 46 | return lr 47 | 48 | return tb | lr 49 | } 50 | 51 | static String toString(Integer align){ 52 | switch (align){ 53 | case Align.center : return "center" 54 | case Align.left : return "left" 55 | case Align.right : return "right" 56 | case Align.bottom : return "bottom" 57 | case Align.bottomLeft : return "bottomLeft" 58 | case Align.bottomRight : return "bottomRight" 59 | case Align.top : return "top" 60 | case Align.topLeft : return "topLeft" 61 | case Align.topRight : return "topRight" 62 | default: return "UNKNOWN(" + align + ")" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/util/BoxParser.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.util 2 | 3 | 4 | import com.badlogic.gdx.scenes.scene2d.ui.Value 5 | import com.badlogic.gdx.utils.Align 6 | import com.steadystate.css.dom.CSSValueImpl 7 | import groovy.transform.CompileStatic 8 | import team.rpsg.html.dom.Dom 9 | 10 | @CompileStatic 11 | class BoxParser { 12 | static void parsePadding(Dom dom){ 13 | float padLeft = 0, padRight = 0, padTop = 0, padBottom = 0 14 | 15 | def padAll = dom.style("padding", null, {r -> r}, false) as CSSValueImpl 16 | 17 | if(padAll) { 18 | 19 | List values = [] 20 | 21 | if (padAll.value instanceof List){ 22 | values = padAll.value as List 23 | }else{ 24 | values << padAll 25 | } 26 | 27 | if(values.size() == 1) { 28 | def v = SizeParser.parse(values[0]) 29 | if(v) 30 | padLeft = padRight = padTop = padBottom = v.get(dom) 31 | }else if(values.size() == 2){ 32 | def v0 = SizeParser.parse(values[0]), v1 = SizeParser.parse(values[1]) 33 | if(v0) 34 | padTop = padBottom = v0.get(dom) 35 | if(v1) 36 | padLeft = padRight = v1.get(dom) 37 | } 38 | } 39 | 40 | padLeft = parseValueInt(dom, "padding-left", padLeft) 41 | padRight = parseValueInt(dom, "padding-right", padRight) 42 | padTop = parseValueInt(dom, "padding-top", padTop) 43 | padBottom = parseValueInt(dom, "padding-bottom", padBottom) 44 | 45 | dom.pad(padTop, padLeft, padBottom, padRight) 46 | 47 | } 48 | 49 | /** 50 | * @return parsed 51 | */ 52 | public static final int NEEDS_SET_ALIGN = 1 53 | 54 | static int parseMargin(Dom dom){ 55 | if(!dom.parentContainer) 56 | return -1 57 | 58 | float mLeft = 0, mRight = 0, mTop = 0, mBottom = 0 59 | boolean mLeftAuto = false, mRightAuto = false 60 | 61 | def mAll = dom.style("margin", null, {r -> r}, false) as CSSValueImpl 62 | 63 | if(mAll) { 64 | 65 | List values = [] 66 | 67 | if (mAll.value instanceof List){ 68 | values = mAll.value as List 69 | }else{ 70 | values << mAll 71 | } 72 | 73 | if(values.size() == 1) { 74 | def v = SizeParser.parse(values[0], true) 75 | if(v instanceof SizeParser.AutoValue){ 76 | mLeftAuto = mRightAuto = true 77 | mLeft = mRight = 0 78 | } else if(v){ 79 | mLeft = mRight = mTop = mBottom = v.get(dom) 80 | } 81 | }else if(values.size() == 2){ 82 | def v0 = SizeParser.parse(values[0]), v1 = SizeParser.parse(values[1], true) 83 | if(v0) 84 | mTop = mBottom = v0.get(dom) 85 | 86 | if(v1 instanceof SizeParser.AutoValue){ 87 | mLeftAuto = mRightAuto = true 88 | mLeft = mRight = 0 89 | }else if(v1){ 90 | mLeft = mRight = v1.get(dom) 91 | } 92 | } 93 | } 94 | 95 | Object _l = parseValue(dom, "margin-left", mLeft) 96 | if(_l instanceof SizeParser.AutoValue){ 97 | mLeftAuto = true 98 | mLeft = 0 99 | } else { 100 | mLeft = _l as int 101 | } 102 | 103 | Object _r = parseValue(dom, "margin-right", mRight) 104 | if(_r instanceof SizeParser.AutoValue){ 105 | mRightAuto = true 106 | mRight = 0 107 | } else { 108 | mRight = _r as int 109 | } 110 | 111 | mTop = parseValueInt(dom, "margin-top", mTop) 112 | mBottom = parseValueInt(dom, "margin-bottom", mBottom) 113 | 114 | dom.parentContainer.pad(mTop, mLeft, mBottom, mRight) 115 | 116 | int align = Align.left 117 | if(mLeftAuto) 118 | align = Align.right 119 | if(mLeftAuto && mRightAuto) 120 | align = Align.center 121 | 122 | def result = 0 123 | if(align != Align.left) 124 | result = NEEDS_SET_ALIGN 125 | 126 | dom.boxAlign = align 127 | 128 | return result 129 | 130 | } 131 | 132 | private static int parseValueInt(Dom dom, String name, float orDefault = 0){ 133 | def result = dom.style(name, orDefault, SizeParser.&parse, false) as Value 134 | 135 | if(!result) 136 | return orDefault 137 | 138 | return result.get(dom) 139 | } 140 | 141 | private static Object parseValue(Dom dom, String name, float orDefault = 0){ 142 | def result = dom.style(name, orDefault, {p -> SizeParser.parse(p, true)}, false) as Value 143 | 144 | if(!result) 145 | return orDefault 146 | 147 | if(result instanceof SizeParser.AutoValue) 148 | return result 149 | 150 | return result.get(dom) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/util/DomParser.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.util 2 | 3 | import groovy.transform.CompileStatic 4 | import org.jsoup.nodes.Element 5 | import org.jsoup.nodes.Node 6 | import org.jsoup.nodes.TextNode 7 | import team.rpsg.html.dom.Dom 8 | import team.rpsg.html.dom.HTMLDomPreset 9 | import team.rpsg.html.dom.UnknownDom 10 | import team.rpsg.html.dom.node.Image 11 | import team.rpsg.html.dom.node.Text 12 | 13 | 14 | @CompileStatic 15 | class DomParser { 16 | /** 17 | * parse jsoup node to renderable GDX-HTML dom. 18 | */ 19 | static Dom parse(Node node){ 20 | switch (node.class){ 21 | 22 | case TextNode.class: 23 | if((node as TextNode).text().trim().length() == 0) 24 | return null 25 | return new Text(node) 26 | 27 | case Element.class: 28 | def ele = node as Element 29 | 30 | def tagName = ele.tagName().toLowerCase() 31 | switch (tagName){ 32 | case "img": return new Image(ele) 33 | } 34 | 35 | 36 | try{ 37 | return HTMLDomPreset.valueOf(ele.tagName().toUpperCase()).dom(node) 38 | }catch (ignored){ 39 | return new UnknownDom(node) 40 | } 41 | 42 | 43 | default: 44 | return new UnknownDom(node) 45 | 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/util/PathParser.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.util 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.Net 5 | import groovy.transform.CompileStatic 6 | import org.jsoup.nodes.Document 7 | 8 | 9 | /** 10 | * parsePadding path 11 | */ 12 | @CompileStatic 13 | class PathParser { 14 | /** 15 | * parsePadding path by document baseURI, return absolute(assets root) path
16 | *
17 | 	 * assets
18 | 	 * ├── img
19 | 	 * │   └── 1.jpg
20 | 	 * ├── html
21 | 	 * │   ├── 1.html
22 | 	 * │   └── 2.html
23 | 	 * 
24 | * assert PathParser.parsePadding(doc, "../img/1.jpg") == "/img/1.jpg";
25 | * assert PathParser.parsePadding(doc, "2.html") == "/html/2.html";
26 | */ 27 | static String parse(Document document, String path){ 28 | return new URI(document.baseUri() ?: "/").resolve(new URI(path)).toString() 29 | } 30 | 31 | 32 | static void get(Document document, String path, String charset, Closure callback){ 33 | if(!charset || charset.length() == 0) 34 | charset = "utf-8" 35 | 36 | def uri = new URI(document.baseUri() ?: "/").resolve(new URI(path)) 37 | 38 | try{ 39 | def request = new Net.HttpRequest(url: uri.toURL().toString(), method: "GET") 40 | 41 | Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener(){ 42 | void handleHttpResponse(Net.HttpResponse httpResponse) { 43 | callback(httpResponse.resultAsString, true) 44 | } 45 | void failed(Throwable t) { 46 | t.printStackTrace() 47 | } 48 | void cancelled() {} 49 | }) 50 | 51 | 52 | }catch(ignored){ 53 | callback(Gdx.files.internal(uri.toString()).readString(charset), false) 54 | } 55 | 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/util/SizeParser.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.util 2 | 3 | import com.badlogic.gdx.scenes.scene2d.Actor 4 | import com.badlogic.gdx.scenes.scene2d.ui.Table 5 | import com.badlogic.gdx.scenes.scene2d.ui.Value 6 | import com.badlogic.gdx.scenes.scene2d.utils.Layout 7 | import com.steadystate.css.dom.CSSValueImpl 8 | import com.steadystate.css.parser.LexicalUnitImpl 9 | import groovy.transform.CompileStatic 10 | import team.rpsg.html.dom.Dom 11 | 12 | /** 13 | * unit(px or %) parser 14 | */ 15 | @CompileStatic 16 | class SizeParser { 17 | /** 18 | * font-size: 22px; 19 | * font-size: auto; 20 | */ 21 | static int parseFontPX(Object property){ 22 | 23 | if(property.toString().indexOf('%') > 0) 24 | return 16 25 | if(property.toString().equals("0")) 26 | return 0 27 | if(property.toString().indexOf('px') > 0) 28 | return property.toString().replace("px", "").toInteger()?: 16 29 | if(property.toString().indexOf('rem') > 0) 30 | return ((property.toString().replace("rem", "").toFloat() * 16f)?: 16) as int 31 | if(property.toString().indexOf('em') > 0) 32 | return ((property.toString().replace("em", "").toFloat() * 16f)?: 16) as int 33 | 34 | return 16 35 | 36 | } 37 | 38 | /** 39 | * width: 80%; 40 | * width: 65px; 41 | * width: auto; 42 | */ 43 | static Value parse(Object property, parseAutoValue = false, Closure valueParser = this.&percentInnerWidth){ 44 | 45 | if(property && property.toString().equalsIgnoreCase("auto")){ 46 | if(parseAutoValue) 47 | return new AutoValue() 48 | return null 49 | } 50 | 51 | if(property instanceof Float) 52 | return new Value.Fixed(property.toFloat()) 53 | 54 | if(property instanceof CSSValueImpl && property.value instanceof LexicalUnitImpl){ 55 | def unit = property.value as LexicalUnitImpl 56 | 57 | if(unit.lexicalUnitType == 17) 58 | return new Value.Fixed(unit.floatValue) 59 | 60 | if(unit.lexicalUnitType == 13 || property.toString().equals("0")) 61 | return new Value.Fixed(0) 62 | 63 | if(unit.lexicalUnitType == 23) 64 | return valueParser((unit.floatValue / 100f) as float) as Value 65 | } 66 | 67 | if(property instanceof String){ 68 | if(property.indexOf('%') > 0) 69 | return valueParser((property.toString().replace("%", "").toFloat() / 100f) as float) as Value 70 | if(property.indexOf("px") > 0) 71 | return new Value.Fixed(property.toString().replace("px", "").toInteger()) 72 | } 73 | 74 | 75 | return null 76 | } 77 | 78 | static Value percentInnerWidth(float p){ 79 | return new percentInnerValue() { 80 | float getPercent() { 81 | p 82 | } 83 | 84 | float get (Actor actor) { 85 | if(actor instanceof Dom) 86 | return (actor as Dom).innerWidth * p 87 | 88 | return (actor as Table).width * p 89 | } 90 | } 91 | } 92 | 93 | static Value percentInnerHeight(float p){ 94 | return new percentInnerValue() { 95 | float getPercent() { 96 | p 97 | } 98 | 99 | float get (Actor actor) { 100 | if(actor instanceof Dom) 101 | return (actor as Dom).innerHeight * p 102 | 103 | return (actor as Table).height * p 104 | } 105 | } 106 | } 107 | 108 | static final Value innerWidth = percentInnerWidth(1) 109 | static final Value innerHeight = percentInnerHeight(1) 110 | 111 | static class AutoValue extends Value{ 112 | float get(Actor context) { 113 | return 0 114 | } 115 | } 116 | 117 | abstract static class percentInnerValue extends Value{ 118 | abstract float getPercent() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/util/StyleParser.groovy: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.util 2 | 3 | import com.steadystate.css.dom.CSSStyleDeclarationImpl 4 | import com.steadystate.css.parser.CSSOMParser 5 | import com.steadystate.css.parser.SACParserCSS3 6 | import groovy.transform.CompileStatic 7 | import org.w3c.css.sac.InputSource 8 | 9 | @CompileStatic 10 | class StyleParser { 11 | /** 12 | * @param styles string, like "font-size: 20px; css-prop: value; ..." 13 | * @return 14 | */ 15 | static CSSStyleDeclarationImpl parse(String stylesString){ 16 | (CSSStyleDeclarationImpl)new CSSOMParser(new SACParserCSS3()).parseStyleDeclaration(new InputSource(new StringReader(stylesString))) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /html/src/team/rpsg/html/util/Timer.java: -------------------------------------------------------------------------------- 1 | package team.rpsg.html.util; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * 延时运行工具
10 | * LibGDX本身也有个{@link Timer}工具,但是他的实现是靠多线程的,为了避免吃到意外的屎,只好造一个轮子了2333,该轮子不依靠线程,而是在主线程的主循环里工作。 11 | */ 12 | public class Timer { 13 | List list = new ArrayList<>(); 14 | List addList = new ArrayList<>(); 15 | 16 | public static final int FOREVER = -1; 17 | boolean pause = false; 18 | 19 | public Task add(TimeType type, int delay, int repeat, Runnable run) { 20 | Task task = new Task(); 21 | task.run = run; 22 | task.time = task.originTime = delay; 23 | task.repeat = repeat; 24 | task.type = type; 25 | addList.add(task); 26 | 27 | return task; 28 | } 29 | 30 | public Task add(TimeType type, int delay, Runnable run) { 31 | return add(type, delay, 1, run); 32 | } 33 | 34 | public void act() { 35 | float delta = Gdx.graphics.getRawDeltaTime(); 36 | 37 | if(!addList.isEmpty()){ 38 | list.addAll(addList); 39 | addList.clear(); 40 | } 41 | 42 | if(!list.isEmpty()){ 43 | List removeList = new ArrayList<>(); 44 | for(Task timer : list){ 45 | if((timer.type == TimeType.frame && timer.time -- < 0) || (timer.type == TimeType.millisecond && (timer.time -= delta * 1000) < 0)){ 46 | timer.run.run(); 47 | if(timer.repeat != FOREVER && -- timer.repeat == 0) 48 | removeList.add(timer.done()); 49 | else 50 | timer.time = timer.originTime; 51 | } 52 | } 53 | 54 | list.removeAll(removeList); 55 | } 56 | } 57 | 58 | public void pause() { 59 | pause = true; 60 | } 61 | 62 | public void resume() { 63 | pause = false; 64 | } 65 | 66 | public void remove(Task timer) { 67 | list.remove(timer); 68 | } 69 | 70 | public void then(Runnable o) { 71 | add(TimeType.frame, 0, o); 72 | } 73 | 74 | public static class Task{ 75 | public int time, originTime; 76 | public int repeat = 1; 77 | public Runnable run; 78 | public TimeType type; 79 | 80 | public boolean done = false; 81 | 82 | public Task done(){ 83 | done = true; 84 | return this; 85 | } 86 | } 87 | 88 | public enum TimeType{ 89 | frame, millisecond 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /readme/show1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/readme/show1.png -------------------------------------------------------------------------------- /readme/show2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dingjibang/GDX-HTML/a30720ed6ba3eded776a54e9095e6fee17416ae7/readme/show2.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'desktop', 'android', 'core' 2 | include 'html' 3 | 4 | --------------------------------------------------------------------------------